# Authenticate to a Zone

:::info
This guide is intended to be low-level. If you are looking for a high-level abstraction, check out
[Viem's Connect to a Zone guide](https://viem.sh/tempo/guides/zones/connect).
:::

## Overview

A Zone RPC authentication token contains a zone ID, zone chain ID, issue time, expiry time, and
signature. [`ZoneRpcAuthentication.from`](/tempo/reference/ZoneRpcAuthentication/from) constructs
the fields, and [`ZoneRpcAuthentication.serialize`](/tempo/reference/ZoneRpcAuthentication/serialize)
encodes the signed token for the `X-Authorization-Token` request header.

The token proves control of a signing key. The zone operator decides whether that account may access
the endpoint and which expiry windows it accepts.

[See the Zone RPC specification](https://docs.tempo.xyz/protocol/zones/rpc#authorization-tokens)

## Recipes

### Create and Sign a Token

Derive the chain ID with [`ZoneId.toChainId`](/tempo/reference/ZoneId/toChainId), then use current
Unix timestamps for the token window. Sign
[`ZoneRpcAuthentication.getSignPayload`](/tempo/reference/ZoneRpcAuthentication/getSignPayload)
before serializing the token.

```ts twoslash
import { Secp256k1 } from 'ox'
import { ZoneId, ZoneRpcAuthentication } from 'ox/tempo'

// 1. Set up the signer, zone, and issue time.
const privateKey = Secp256k1.randomPrivateKey()
const zoneId = 5
const issuedAt = Math.floor(Date.now() / 1_000)

// [!code focus:start]
// 2. Create the authentication claims.
const authentication = ZoneRpcAuthentication.from({
  chainId: ZoneId.toChainId(zoneId), // [!code hl]
  expiresAt: issuedAt + 10 * 60, // [!code hl]
  issuedAt,
  zoneId,
})

// 3. Sign the authentication payload.
const signature = Secp256k1.sign({
  payload: ZoneRpcAuthentication.getSignPayload(authentication),
  privateKey,
})

// 4. Serialize the signed token.
const serialized = ZoneRpcAuthentication.serialize(authentication, {
  signature,
})
// [!code focus:end]
```

The example uses a ten-minute lifetime. Follow the zone operator's accepted lifetime and clock-skew
policy when choosing `issuedAt` and `expiresAt`.

### Attach the Token to an RPC Transport

Pass the serialized token through [`RpcTransport.fromHttp`](/api/RpcTransport/fromHttp) with
[`ZoneRpcAuthentication.headerName`](/tempo/reference/ZoneRpcAuthentication). The computed property
uses the exact `X-Authorization-Token` header name expected by Zone RPC endpoints.

```ts twoslash
import { RpcTransport, Secp256k1 } from 'ox'
import { ZoneId, ZoneRpcAuthentication } from 'ox/tempo'

// 1. Set up the signer, zone, and issue time.
const privateKey = Secp256k1.randomPrivateKey()
const zoneId = 5
const issuedAt = Math.floor(Date.now() / 1_000)

// 2. Create the authentication claims.
const authentication = ZoneRpcAuthentication.from({
  chainId: ZoneId.toChainId(zoneId),
  expiresAt: issuedAt + 10 * 60,
  issuedAt,
  zoneId,
})

// 3. Sign and serialize the token.
const serialized = ZoneRpcAuthentication.serialize(authentication, {
  signature: Secp256k1.sign({
    payload: ZoneRpcAuthentication.getSignPayload(authentication),
    privateKey,
  }),
})

// [!code focus:start]
// 4. Attach the token to the RPC transport.
const transport = RpcTransport.fromHttp('https://zone.example/rpc', {
  fetchOptions: {
    headers: {
      [ZoneRpcAuthentication.headerName]: serialized, // [!code hl]
    },
  },
})
// 5. Make an authenticated RPC request.
const blockNumber = await transport.request({
  method: 'eth_blockNumber',
})
// @log: '0x...'
// [!code focus:end]
```

Add any separate operator credentials to `headers` alongside the Zone token. Ox does not infer an
endpoint or its additional authentication requirements.

### Inspect a Serialized Token

[`ZoneRpcAuthentication.deserialize`](/tempo/reference/ZoneRpcAuthentication/deserialize) recovers
the signed fields. Use it to inspect cached credentials before deciding whether to refresh them.

```ts twoslash
import { Secp256k1 } from 'ox'
import { ZoneId, ZoneRpcAuthentication } from 'ox/tempo'

// 1. Set up and sign a short-lived token.
const privateKey = Secp256k1.randomPrivateKey()
const zoneId = 5
const issuedAt = Math.floor(Date.now() / 1_000)
const authentication = ZoneRpcAuthentication.from({
  chainId: ZoneId.toChainId(zoneId),
  expiresAt: issuedAt + 10 * 60,
  issuedAt,
  zoneId,
})
const serialized = ZoneRpcAuthentication.serialize(authentication, {
  signature: Secp256k1.sign({
    payload: ZoneRpcAuthentication.getSignPayload(authentication),
    privateKey,
  }),
})

// [!code focus:start]
// 2. Decode the cached token.
const decoded = ZoneRpcAuthentication.deserialize(serialized)

// 3. Check whether its validity window has ended.
const isExpired = decoded.expiresAt <= Math.floor(Date.now() / 1_000) // [!code hl]
// @log: false
// [!code focus:end]
```

Deserialization verifies the encoding shape and version. It does not establish that the signature is
authorized by a particular zone.

## Best Practices

### Keep Tokens Short-Lived

Use the shortest lifetime that fits the session and refresh before expiry. Allow only the clock skew
documented by the zone operator.

### Keep Zone IDs Consistent

Derive `chainId` from the same `zoneId` instead of maintaining both independently. This prevents a
credential from being signed for mismatched replay-protection fields.

### Protect Serialized Tokens

Treat a serialized token as a credential. Avoid logging it, leaking it to third parties, or storing
it beyond its useful lifetime.

### Separate Authentication from Authorization

Ox proves key control and encodes the request credential. The zone server remains responsible for
account admission, authorization policy, and expiry enforcement.

## See More

<Cards>
  <Card icon="lucide:key-round" title="Access Keys" description="Create account-bound keys for delegated signing flows." to="/tempo/guides/access-keys" />

  <Card icon="lucide:square-function" title="ZoneRpcAuthentication.serialize" description="Review supported signature envelope inputs." to="/tempo/reference/ZoneRpcAuthentication/serialize" />

  <Card icon="lucide:book-open" title="Viem: Connect to a Zone" description="Authenticate a Viem client with a private Zone." to="https://viem.sh/tempo/guides/zones/connect" />
</Cards>
