<!--
Sitemap:
- [Tempo Accounts SDK - Getting Started](/docs/): Set up the Tempo Accounts SDK to create, manage, and interact with accounts on Tempo.
- [Deploying to Production](/docs/production): Things to consider before deploying your application with the Tempo Accounts SDK to production.
- [FAQ](/docs/faq): Frequently asked questions about the Tempo Accounts SDK.
- [Connect Accounts](/docs/guides/connect-accounts): Connect a Tempo account in your application.
- [Authentication](/docs/guides/authentication): Authenticate connected accounts against your own server with a signed SIWE challenge.
- [Identity](/docs/guides/identity): Request and verify identity claims about a connected account, starting with a verified email.
- [Transfers](/docs/guides/transfers): Send stablecoin transfers from a connected Tempo account, initiated either by the user or by your server.
- [Spend Permissions](/docs/guides/spend-permissions): Authorize spend limits, call scopes, and expiries so repeat transfers can be signed without a confirmation prompt.
- [React Native](/docs/guides/react-native): Set up Tempo Accounts in a React Native app.
- [Subscriptions](/docs/guides/subscriptions): Charge recurring payments from a connected Tempo account.
- [Fee Sponsorship](/docs/guides/fee-sponsorship): Sponsor transaction fees from a server-controlled policy.
- [Deposits](/docs/guides/deposits): Open the Tempo deposit flow from a connected account and let the user choose deposit details.
- [Swaps](/docs/guides/swaps): Open the Tempo swap flow from a connected account with optional pre-filled intent fields.
- [Theming](/docs/guides/theming): Match embedded account surfaces to your product.
- [CLI](/docs/guides/cli): Authorize and use Tempo accounts from command-line tools.
- [Adapters](/docs/adapters/): Choose the signing adapter for your Tempo Accounts SDK integration.
- [Tempo Wallet Adapter](/docs/adapters/tempo-wallet): Use Tempo Wallet as the hosted universal wallet adapter.
- [WebAuthn Adapter](/docs/adapters/webauthn): Use domain-bound passkeys as the account signing adapter.
- [Turnkey Adapter](/docs/adapters/turnkey): Use Turnkey-managed wallet accounts as the account signing adapter.
- [Privy Adapter](/docs/adapters/privy): Use Privy embedded wallets as the account signing adapter.
- [Private Key Adapter](/docs/adapters/private-key): Sign in-process with a `secp256k1` private key.
- [Custom Adapter](/docs/adapters/custom): Author your own adapter with the `Adapter.define` API.
- [Adapters](/docs/api/adapters): Pluggable adapters for the Tempo Accounts SDK Provider.
- [dialog](/docs/api/dialog): Adapter for the Tempo Wallet dialog, an embedded iframe or popup for account management.
- [local](/docs/api/local): Key-agnostic adapter for defining arbitrary account types and signing mechanisms.
- [mobileWebAuth](/docs/api/mobileWebAuth): Mobile web auth adapter for browser-session wallet requests.
- [postMessage](/docs/api/postMessage): Connect to wallet from anywhere on the web
- [privy](/docs/api/privy): React adapter backed by Privy sessions and embedded Ethereum wallets.
- [secp256k1](/docs/api/secp256k1): Adapter that signs in-process with a `secp256k1` private key.
- [turnkey](/docs/api/turnkey): Adapter backed by Turnkey client sessions.
- [webAuthn](/docs/api/webAuthn): Adapter for passkey-based accounts using WebAuthn registration and authentication.
- [Dialog](/docs/api/dialogs): Dialog modes for embedding the Tempo Wallet.
- [Dialog.iframe](/docs/api/dialog.iframe): Embed the Tempo Wallet auth UI in an iframe dialog element.
- [Dialog.popup](/docs/api/dialog.popup): Open the Tempo Wallet auth UI in a popup window.
- [Expiry](/docs/api/expiry): Utility functions for computing access key expiry timestamps.
- [Provider](/docs/api/provider): Create an EIP-1193 provider for managing accounts on Tempo.
- [Rpc](/docs/api/rpc): Per-method Zod schemas and shared building blocks for the Accounts JSON-RPC surface.
- [Schema](/docs/api/schema): Zod-based JSON-RPC schema definitions for the Accounts provider.
- [TrustedHosts](/docs/api/trustedHosts): Trusted host mappings and matching helpers for dialog adapters.
- [WebAuthnCeremony](/docs/api/webauthnceremony): Pluggable strategy for WebAuthn registration and authentication ceremonies.
- [WebAuthnCeremony.from](/docs/api/webauthnceremony.from): Create a WebAuthnCeremony from a custom implementation.
- [WebAuthnCeremony.server](/docs/api/webauthnceremony.server): Server-backed WebAuthn ceremony that delegates to a remote handler.
- [CLI](/docs/cli/provider): Create a Provider for CLI environments.
- [eth_accounts](/docs/rpc/eth_accounts): Get the addresses of the currently connected accounts.
- [eth_chainId](/docs/rpc/eth_chainId): Get the chain ID of the currently active chain.
- [eth_fillTransaction](/docs/rpc/eth_fillTransaction): Fills missing transaction fields and returns wallet-aware metadata.
- [eth_requestAccounts](/docs/rpc/eth_requestAccounts): Request access to user accounts, prompting the user to connect if needed.
- [eth_sendTransaction](/docs/rpc/eth_sendTransaction): Send a transaction from the connected account.
- [eth_sendTransactionSync](/docs/rpc/eth_sendTransactionSync): Send a transaction and wait for the receipt.
- [eth_signTransaction](/docs/rpc/eth_signTransaction): Sign a transaction without broadcasting it.
- [eth_signTypedData_v4](/docs/rpc/eth_signTypedData_v4): Sign EIP-712 typed structured data with the connected account.
- [personal_sign](/docs/rpc/personal_sign): Sign a message with the connected account.
- [wallet_authorizeAccessKey](/docs/rpc/wallet_authorizeAccessKey): Authorize an access key for delegated transaction signing.
- [wallet_connect](/docs/rpc/wallet_connect): Connect account(s) with optional capabilities like access key authorization.
- [wallet_deposit](/docs/rpc/wallet_deposit): Open the wallet deposit flow with optional pre-filled fields.
- [wallet_depositZone](/docs/rpc/wallet_depositZone): Open the wallet zone-deposit flow with optional pre-filled fields.
- [wallet_disconnect](/docs/rpc/wallet_disconnect): Disconnect the connected account(s).
- [wallet_getBalances](/docs/rpc/wallet_getBalances): Get token balances for an account.
- [wallet_getCallsStatus](/docs/rpc/wallet_getCallsStatus): Get the status of a batch of calls sent via wallet_sendCalls.
- [wallet_getCapabilities](/docs/rpc/wallet_getCapabilities): Get account capabilities for specified chains.
- [wallet_revokeAccessKey](/docs/rpc/wallet_revokeAccessKey): Revoke a previously authorized access key.
- [wallet_send](/docs/rpc/wallet_send): Open the wallet send-token flow with optional pre-filled fields.
- [wallet_sendCalls](/docs/rpc/wallet_sendCalls): Send a batch of calls from the connected account.
- [wallet_swap](/docs/rpc/wallet_swap): Open the wallet swap flow with optional pre-filled swap intent fields.
- [wallet_switchEthereumChain](/docs/rpc/wallet_switchEthereumChain): Switch the provider's active chain.
- [wallet_withdrawZone](/docs/rpc/wallet_withdrawZone): Open the wallet zone-withdraw flow with optional pre-filled fields.
- [Remote](/docs/api/remote): Bridge that runs inside the wallet's iframe/popup and serves RPC requests from the host SDK.
- [Remote.create](/docs/api/remote.create): Create a remote context bound to a Messenger and Provider.
- [Remote.useEnsureVisibility](/docs/api/remote.useEnsureVisibility): React hook that monitors iframe visibility and falls back to a popup when occluded.
- [Remote.useState](/docs/api/remote.useState): React hook to subscribe to a remote context's state store.
- [Remote.useTheme](/docs/api/remote.useTheme): React hook that applies theme overrides from URL search params and live messenger updates.
- [Remote.validateSearch](/docs/api/remote.validateSearch): Validate an RPC request payload from URL search params.
- [Tempo Accounts Server Handlers](/docs/server/): Configure server-side Tempo Accounts SDK handlers for relaying wallet RPC requests, composing backends, and managing WebAuthn ceremonies.
- [Handler.auth](/docs/server/handler.auth): Server handler that issues SIWE-based authentication challenges and sessions.
- [Handler.codeAuth](/docs/server/handler.codeAuth): Server handler for the device-code (PKCE) access-key bootstrap flow.
- [Handler.compose](/docs/server/handler.compose): Compose multiple server handlers into a single handler.
- [Handler.exchange](/docs/server/handler.exchange): Server handler that returns Stablecoin DEX quotes and ready-to-submit calls.
- [Handler.relay](/docs/server/handler.relay): Server handler that proxies certain RPC requests with wallet-aware enrichment.
- [Handler.webAuthn](/docs/server/handler.webAuthn): Server-side WebAuthn ceremony handler for registration and authentication.
- [hc](/docs/server/hc): Typed RPC client for handlers built with the Tempo Accounts SDK.
- [Identity.verify](/docs/server/identity.verify): Verify a wallet-issued identity token (verified email) against an issuer's JWKS.
- [Kv](/docs/server/kv): Key-value store adapters for server-side persistence.
- [Kv.cloudflare](/docs/server/kv.cloudflare): Kv adapter backed by a Cloudflare Workers KV namespace.
- [Kv.durableObject](/docs/server/kv.durableObject): Kv adapter backed by a Cloudflare Durable Object with atomic take and create.
- [Kv.from](/docs/server/kv.from): Wrap a custom Kv-shaped object so the SDK accepts it as a Kv.
- [Kv.memory](/docs/server/kv.memory): In-memory Kv adapter for tests and single-process deployments.
- [Keystore](/docs/api/keystore): Pluggable backends for key material.
- [Keystore.p256](/docs/api/keystore.p256): Pure-JS P-256 keystore.
- [Keystore.secp256k1](/docs/api/keystore.secp256k1): Pure-JS secp256k1 keystore.
- [Keystore.webCryptoP256](/docs/api/keystore.webCryptoP256): WebCrypto P-256 keystore.
- [Storage](/docs/api/storage): Pluggable storage adapters for persisting provider state.
- [Storage.combine](/docs/api/storage.combine): Combine multiple Storage adapters into one.
- [Storage.cookie](/docs/api/storage.cookie): Cookie-backed Storage adapter.
- [Storage.from](/docs/api/storage.from): Create a Storage adapter from a custom implementation.
- [Storage.idb](/docs/api/storage.idb): IndexedDB-backed Storage adapter.
- [Storage.localStorage](/docs/api/storage.localStorage): localStorage-backed Storage adapter.
- [Storage.memory](/docs/api/storage.memory): In-memory Storage adapter.
- [asyncStorage](/docs/api/storage.asyncStorage): React Native Storage adapter backed by AsyncStorage.
- [secureMmkv](/docs/api/storage.secureMmkv): Encrypted React Native Storage adapter backed by MMKV.
- [Tempo Accounts SDK](/index): The fastest way to add stablecoins to your application.
- [Secp256k1 Adapter](/docs/adapters/secp256k1): Sign in-process with a `secp256k1` private key.
- [Bring Your Auth](/docs/enterprise/bring-your-auth/): Connect enterprise auth and signing systems to Tempo accounts.
- [Hosted Universal Wallets](/docs/enterprise/hosted-universal-wallets): Stub for hosting a universal wallet on your own domain.
- [Handler.feePayer (Deprecated)](/docs/server/handler.feePayer): Deprecated — use Handler.relay with feePayer option instead.
- [tempoWallet](/docs/wagmi/tempoWallet): Wagmi connector for the Tempo Wallet dialog.
- [webAuthn](/docs/wagmi/webAuthn): Wagmi connector for passkey-based WebAuthn accounts.
- [AWS KMS](/docs/enterprise/bring-your-auth/aws-kms): Stub for integrating AWS KMS-backed signing with the Tempo Accounts SDK.
- [Custom Auth](/docs/enterprise/bring-your-auth/custom): Stub for first-party enterprise auth and signing integrations.
- [Privy](/docs/enterprise/bring-your-auth/privy): Enterprise notes for integrating Privy-backed auth with the Tempo Accounts SDK.
- [Turnkey](/docs/enterprise/bring-your-auth/turnkey): Stub for integrating Turnkey-backed signing with the Tempo Accounts SDK.
-->

# `Handler.auth`

Creates a server handler that issues SIWE-style authentication challenges and verifies signed messages. Mounts three POST endpoints under `path`:

* `POST {path}/challenge` — issue a single-use challenge message (`{ message }`)
* `POST {path}` — verify the signed message and issue a session (cookie via `Set-Cookie`, or `{ token }` body when `cookie: false` or the request opts in via `returnToken: true`)
* `POST {path}/logout` — revoke the session and clear the cookie. Only mounted when [`session`](#session) is `true` (the default); otherwise calls fall through as `404`.

The challenge wire format is [EIP-4361 (SIWE)](https://eips.ethereum.org/EIPS/eip-4361) for ecosystem compatibility, but address binding is deferred — the wallet supplies the signing address at verify time and the server uses it as the session subject.

## Usage

```ts
import { Handler } from 'accounts/server'

const handler = Handler.auth()
```

Then plug `handler` into your server framework of choice:

```ts
createServer(handler.listener)              // Node.js
Bun.serve({ fetch: handler.fetch })         // Bun
Deno.serve({ fetch: handler.fetch })        // Deno
app.all('*', c => handler.fetch(c.request)) // Elysia
app.use(handler.listener)                   // Express
app.use(c => handler.fetch(c.req.raw))      // Hono
export const GET = handler.fetch            // Next.js
export const POST = handler.fetch           // Next.js
```

### Validating & Extracting Sessions

Use the [`getSession`](#getsession) helper attached to the handler to resolve the current session inside other routes on the same server. It reads the session cookie (or `Authorization: Bearer <token>` header) and returns the [`SessionPayload`](#sessionpayload), or `undefined` when the request is unauthenticated.

```ts
import { Handler } from 'accounts/server'

const handler = Handler.auth()

app.get('/me', async (req) => {
  const session = await handler.getSession(req)
  if (!session) return new Response('unauthorized', { status: 401 })
  return Response.json({ address: session.address })
})
```

## Return Type

`Handler.auth` returns a [`Handler`](/docs/server/handler.compose#handler) extended with a `getSession` helper for resolving the current session from a follow-up request:

```ts
type ReturnType = Handler & {
  getSession: (req: Request) => Promise<SessionPayload | undefined>
}
```

#### `getSession`

Resolves the current session from a request's session cookie or `Authorization: Bearer <token>` header. Returns `undefined` when no token is present, the token has been revoked, or the session has been disabled (`session: false`).

```ts
import { Handler } from 'accounts/server'

const handler = Handler.auth()

// Inside another route on the same server:
app.get('/me', async (req) => {
  const session = await handler.getSession(req)
  if (!session) return new Response('unauthorized', { status: 401 })
  return Response.json({ address: session.address })
})
```

### `SessionPayload`

```ts
type SessionPayload = {
  /** Address of the account that signed the challenge. */
  address: Address
  /** Chain ID echoed into the challenge message. */
  chainId: number
  /**
   * Verified email, set when the verify request included a valid identity
   * token and [`identity`](#identity) is configured. Omitted otherwise.
   */
  email?: string | undefined
  /** Unix timestamp (seconds) when the session was issued. */
  issuedAt: number
  /** Unix timestamp (seconds) when the session expires. */
  expiresAt: number
}
```

The session payload is persisted in [`store`](#store) under `session:{token}` and is also the value returned by [`getSession`](#getsession).

## Parameters

### cookie

* **Type:** `boolean`
* **Default:** `true`

Whether to issue a session cookie on successful verify. When `false`:

* the verify response always contains `{ token }` in the body (the per-request `returnToken` flag is ignored)
* no `Set-Cookie` header is sent
* logout does not clear a cookie
* `getSession` ignores any incoming cookie -- only `Authorization: Bearer <token>` is honored

Use this when the SDK lives in a non-browser context or the host app already manages its own auth cookies.

### cookieName

* **Type:** `string`
* **Default:** `'accounts_auth'`

Cookie name for the session token.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.auth({
  cookieName: 'my_app_session', // [!code focus]
})
```

### domain

* **Type:** `string`
* **Default:** request `Host` header

Domain echoed into the SIWE challenge message. When omitted, the handler derives it from the request `Host` header (or, if [`origin`](#origin) is set, from that URL).

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.auth({
  domain: 'app.example.com', // [!code focus]
})
```

### identity

* **Type:** `{ issuer?: string | undefined; required?: boolean | undefined }`
* **Optional**

Verified-email identity (OIDC), **enabled by default**. A
verify request carrying an `idToken` (minted by the wallet during
[`wallet_connect`](/docs/rpc/wallet_connect#requesting-a-verified-email)) is
verified against the issuer's JWKS, with `aud` pinned to this request's resolved
origin, `sub` cross-checked against the signed-in address, and `nonce` matched to
the SIWE nonce. On success the verified email is folded onto the session as
[`SessionPayload.email`](#sessionpayload).

The wallet only mints tokens when the app requests identity, so requests without
one are unaffected — they still issue a session, just without an email. You only
need to set this option to customize the behavior below.

Reusing the SIWE nonce as the token nonce means the single-use challenge store
covers replay protection for both the signature and the token, so your app needs
no separate nonce plumbing.

* `issuer` — the wallet's identity issuer URL (whose JWKS signs the token). Defaults to `https://wallet.tempo.xyz/api/oidc`; override for a self-hosted wallet.
* `required` — when `true`, reject the verify if no valid token is supplied (defaults to `false`, attach the email when present and proceed otherwise).

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.auth({
  identity: { required: true }, // [!code focus]
})
```

For verifying a token outside of `Handler.auth` (for example when minting your
own session in [`onAuthenticate`](#onauthenticate) with `session: false`), use
[`Identity.verify`](/docs/server/identity.verify).

### onAuthenticate

* **Type:** `(params: { address: Address; chainId: number; message: string; request: Request; signature: Hex }) => Response | Promise<Response> | void | Promise<void>`
* **Optional**

Called after a successful SIWE signature verification, before the session token is issued.

* Returning a `Response` merges its JSON body and status onto the default verify response.
* Returning `void` (or a `Promise<void>`) leaves the default response untouched.
* Throwing rejects the request with `401`. The thrown error's `message` is surfaced as the response `error` field, the challenge is consumed, and no session is issued.

```ts twoslash
import { Handler } from 'accounts/server'

declare function isBlocked(address: string): Promise<boolean>
// ---cut---
const handler = Handler.auth({
  onAuthenticate: async ({ address }) => { // [!code focus]
    if (await isBlocked(address)) throw new Error('address blocked') // [!code focus]
  }, // [!code focus]
})
```

### origin

* **Type:** `string`
* **Optional**

Pinned canonical public origin (e.g. `'https://app.example.com'`). When set, the SIWE `domain` and `uri`, and the cookie `Secure` flag, are derived from this URL — request `Host`, request URL, and `x-forwarded-*` headers are ignored. Recommended for production deployments behind a CDN or reverse proxy: it prevents a spoofed `x-forwarded-host` from shifting the SIWE domain and a spoofed `x-forwarded-proto: http` from disabling `Secure`.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.auth({
  origin: 'https://app.example.com', // [!code focus]
})
```

### path

* **Type:** `string`
* **Default:** `'/'`

Path prefix for the auth endpoints.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.auth({
  path: '/auth', // [!code focus]
})
```

### session

* **Type:** `boolean`
* **Default:** `true`

Whether to issue a session on successful verify. When `false`, verify acts as a stateless signature check:

* no token is generated
* no entry is written to the session store
* no cookie is sent

The verify response is `{}`. `getSession` always returns `undefined` and the `/logout` route is not mounted (so it returns `404`). Use this when the host application mints its own session token (e.g. a JWT inside `onAuthenticate`).

### store

* **Type:** [`Kv`](/docs/server/kv)
* **Default:** `Kv.memory()`

Backing store for both single-use challenges (nonces) and issued sessions. Keys are namespaced internally (`challenge:…`, `session:…`).

:::warning
For one-time-consume nonce semantics across multiple worker instances, use a linearizable backend such as [`Kv.durableObject`](/docs/server/kv.durableObject). [`Kv.cloudflare`](/docs/server/kv.cloudflare) is eventually consistent and is rejected at construction time when used as the auth store on Cloudflare Workers.
:::

```ts twoslash
// @noErrors
import { Handler, Kv } from 'accounts/server'

const handler = Handler.auth({
  store: Kv.durableObject(env.NONCE_DO), // [!code focus]
})
```

### transport

* **Type:** `Transport` (from `viem`)
* **Default:** `http()`

Viem transport for the Tempo client used to verify signatures. The client is always built against the `tempo` chain — Tempo's `chain.verifyHash` natively understands `SignatureEnvelope` for WebAuthn / P256 / keychain signatures and falls back to ECDSA recovery for plain EOAs.

```ts twoslash
import { http } from 'viem'
import { Handler } from 'accounts/server'

const handler = Handler.auth({
  transport: http('https://rpc.tempo.xyz'), // [!code focus]
})
```

### trustProxy

* **Type:** `boolean`
* **Default:** `true` on Cloudflare Workers (always edge-fronted), `false` elsewhere.

Honor `x-forwarded-proto` / `x-forwarded-host` to derive the public origin. Required when running behind a trusted reverse proxy that terminates TLS. When `false`, forwarded headers are ignored to prevent spoofing on deployments that expose the origin server directly. Ignored when [`origin`](#origin) is set.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.auth({
  trustProxy: true, // [!code focus]
})
```

### ttl

* **Type:** `{ challenge?: number; session?: number }`
* **Optional**

TTLs in seconds for stored challenges and issued sessions.

#### ttl.challenge

* **Type:** `number`
* **Default:** `600` (10 minutes)

Maximum age of a challenge in seconds before it expires.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.auth({
  ttl: { challenge: 60 * 5 }, // 5 minutes // [!code focus]
})
```

#### ttl.session

* **Type:** `number`
* **Default:** `86400` (24 hours)

Lifetime of an issued session in seconds. After expiry, `getSession` returns `undefined` and the store entry is evicted by the underlying backend.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.auth({
  ttl: { session: 60 * 60 * 24 * 7 }, // 7 days // [!code focus]
})
```

### cors

* **Type:** `boolean | Cors`
* **Default:** `true`

Inherited from [`from.Options`](/docs/server/handler.compose#optionscors). CORS configuration applied to every response.

### headers

* **Type:** `Headers | Record<string, string>`
* **Optional**

Inherited from [`from.Options`](/docs/server/handler.compose#optionsheaders). Additional headers merged onto every response.
