<!--
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.
-->

# Authentication

Issue a server-side session for a connected Tempo account. The wallet signs a [SIWE](https://eips.ethereum.org/EIPS/eip-4361) challenge during `wallet_connect`, and your server verifies it to authenticate follow-up requests.

The cookie is issued by **your origin**. The SDK calls your `verify` endpoint from the app so the browser attaches the `Set-Cookie` to your domain.

## Demo

By the end of this guide, you will be able to authenticate the connected account against your own server and read the session from any request.

Hit **GET /me** before signing in to confirm the endpoint returns `401`. Sign in, then fetch again to see the authenticated response.

<Demo title="Authenticate an Account" githubUrl="https://github.com/tempoxyz/accounts/tree/main/examples/authentication" headerAction={<DemoReset />}>
  <Authenticate />
</Demo>

## Walkthrough

::::steps

### Install the SDK

Install `accounts` together with Wagmi, Viem, and a server framework. This guide uses [Hono](https://hono.dev) on Cloudflare Workers; the same handler shape works on Node.js, Bun, Deno, Express, and Next.js.

:::code-group

```bash [npm]
npm i accounts wagmi viem hono
```

```bash [pnpm]
pnpm i accounts wagmi viem hono
```

```bash [bun]
bun i accounts wagmi viem hono
```

:::

### Set up the Auth Handler

Create a [`Handler.auth`](/docs/server/handler.auth) instance on your server. It mounts three endpoints under `path`:

* `POST /auth/challenge` — issue a single-use SIWE challenge
* `POST /auth` — verify a signed challenge and issue a session
* `POST /auth/logout` — revoke a session

It also exposes a `getSession` helper for reading the session from other routes.

```ts twoslash [worker/index.ts]
// @noErrors
import { Handler, Kv } from 'accounts/server'
import { env } from 'cloudflare:workers'

// `Kv.durableObject(env.NONCE_DO)` gives the auth handler a linearizable
// store for one-time-consume SIWE challenge nonces and issued sessions
// across concurrent worker instances.
export const auth = Handler.auth({
  path: '/auth',
  store: Kv.durableObject(env.NONCE_DO),
})
```

:::tip\[Pick the right store for production]
The default `Kv.memory()` store is fine for development. Multi-instance deployments need a linearizable backend so a SIWE nonce can only be consumed once.

On Cloudflare, use [`Kv.durableObject`](/docs/server/kv.durableObject). Elsewhere, any [`Kv`](/docs/server/kv) implementation that supports atomic `take` works.
:::

### Mount the Handler

Wire the handler into your server, and expose any routes that should be authenticated by reading the session via `getSession`.

```ts twoslash [worker/index.ts]
// @noErrors
import { Handler, Kv } from 'accounts/server'
import { env } from 'cloudflare:workers'
import { Hono } from 'hono'

// Re-export the reference Durable Object class so wrangler can wire it up.
export const NonceStorage = Kv.NonceStorage

const auth = Handler.auth({
  path: '/auth',
  store: Kv.durableObject(env.NONCE_DO),
})

const app = new Hono()

// Forward every request under `/auth` to the auth handler so the SDK // [!code ++]
// can hit `/auth/challenge`, `/auth`, and `/auth/logout`. // [!code ++]
app.all('/auth', (c) => auth.fetch(c.req.raw)) // [!code ++]
app.all('/auth/*', (c) => auth.fetch(c.req.raw)) // [!code ++]

// Reads the SIWE-issued session and returns the connected address. // [!code ++]
// Demonstrates how an authenticated endpoint consumes Handler.auth. // [!code ++]
app.get('/me', async (c) => { // [!code ++]
  const session = await auth.getSession(c.req.raw) // [!code ++]
  if (!session) return c.json({ error: 'unauthenticated' }, 401) // [!code ++]
  return c.json({ address: session.address, chainId: session.chainId }) // [!code ++]
}) // [!code ++]

export default app
```

:::tip\[Using a different framework?]
`Handler.auth` exposes both `fetch` (Web Fetch API) and `listener` (Node.js `RequestListener`) entrypoints, so you can plug it into any server framework:

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

:::

### Configure the Connector

Pass the auth endpoint to the connector via the `auth` capability. The SDK runs the SIWE round-trip inside `wallet_connect`, sharing the same passkey ceremony.

```tsx twoslash [src/config.ts]
// @noErrors
import { createConfig, http } from 'wagmi'
import { tempo, tempoModerato } from 'wagmi/chains'
import { tempoWallet } from 'wagmi/connectors'

export const config = createConfig({
  chains: [tempoModerato, tempo],
  connectors: [tempoWallet({ auth: '/auth' })], // [!code ++]
  multiInjectedProviderDiscovery: false,
  transports: {
    [tempo.id]: http(),
    [tempoModerato.id]: http(),
  },
})
```

:::tip\[Object form]
Use the [object form](/docs/rpc/wallet_connect) to override individual endpoints, opt into a `{ token }` body via `returnToken`, or split challenge/verify across hosts.

```ts
tempoWallet({
  auth: {
    challenge: 'https://auth.example.com/challenge',
    verify: 'https://auth.example.com/verify',
    logout: 'https://auth.example.com/logout',
    returnToken: true,
  },
})
```

:::

### Display the Sign-In Button

Drop in a connect button — no auth-specific code required. The Wagmi `connect` mutation triggers `wallet_connect`, and the auth round-trip happens inside that call.

```tsx twoslash [src/App.tsx]
// @noErrors
import { useConnect, useConnection, useConnectors, useDisconnect } from 'wagmi'

function Connect() {
  const { address } = useConnection()
  const { mutate: connect, isPending } = useConnect()
  const { mutate: disconnect } = useDisconnect()
  const [connector] = useConnectors()

  if (address)
    return (
      <button onClick={() => disconnect()}>
        Sign out
      </button>
    )

  return (
    <button disabled={isPending} onClick={() => connect({ connector })}>
      Sign in
    </button>
  )
}
```

### Call authenticated resource

With the session cookie attached to your origin, any authenticated route just works. Use `credentials: 'include'` so the cookie is sent on the fetch.

```tsx twoslash [src/App.tsx]
// @noErrors
import { useState } from 'react'

function Me() {
  const [me, setMe] = useState<unknown>(null)

  async function fetchMe() {
    const res = await fetch('/me', { credentials: 'include' }) // [!code focus]
    setMe(await res.json())
  }

  return (
    <div>
      <button onClick={fetchMe}>GET /me</button>
      <pre>{JSON.stringify(me, null, 2)}</pre>
    </div>
  )
}
```

### Sign Out

You don't need to call `/auth/logout` yourself: `wallet_disconnect` automatically POSTs to the configured `logout` endpoint, which clears the session cookie. The next `GET /me` returns `401`.

```tsx twoslash
// @noErrors
import { useDisconnect } from 'wagmi'

const { mutate: disconnect } = useDisconnect()

// Clears the wallet connection AND the server session cookie.
disconnect()
```

### Deploy to Production

When you're ready to go live, follow the [Deploying to Production](/docs/production) guide. Then pin a canonical [`origin`](/docs/server/handler.auth#origin) on `Handler.auth` to prevent a spoofed `x-forwarded-host` from shifting the SIWE domain.

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

const auth = Handler.auth({
  origin: 'https://app.example.com', // [!code ++]
  path: '/auth',
  store: Kv.durableObject(env.NONCE_DO),
})
```

::::

## Recipes

### Request a Verified Email

Alongside the authentication session, you can request the account's verified email via the `identity` capability.

Set `withCapabilities: true` so the connect result includes per-account `capabilities`, then read the email from the authenticated account.

```tsx twoslash [src/App.tsx]
// @noErrors
import { useConnect, useConnectors } from 'wagmi'

function Connect() {
  const { connectAsync } = useConnect()
  const [connector] = useConnectors()

  return (
    <button
      onClick={async () => {
        const result = await connectAsync({
          connector,
          capabilities: { // [!code focus]
            identity: { email: true }, // [!code focus]
          }, // [!code focus]
          withCapabilities: true, // [!code focus]
        })

        // Present only if the user approved sharing. // [!code focus]
        const email = result.accounts[0]?.capabilities?.identity?.email // [!code focus]
      }}
    >
      Sign in
    </button>
  )
}
```

:::warn
The plain `email` string is fine for client-side display, but anyone can set it. The result also carries a signed `idToken` that your server can verify to trust the email. See [Verify the Email Server-Side](#verify-the-email-server-side).
:::

#### Verify the Email Server-Side

Alongside the plain `email`, the wallet returns a signed token proving the email belongs to the account. Your auth handler checks the signature and adds the verified email to the session automatically — set [`identity`](/docs/server/handler.auth#identity) only to require an email or use your own wallet.

```ts twoslash [worker/index.ts]
// @noErrors
import { Handler, Kv } from 'accounts/server'
import { env } from 'cloudflare:workers'

const auth = Handler.auth({
  path: '/auth',
  store: Kv.durableObject(env.NONCE_DO),
})

// In an authenticated route, the verified email is on the session. // [!code focus]
app.get('/me', async (c) => { // [!code focus]
  const session = await auth.getSession(c.req.raw) // [!code focus]
  if (!session) return c.json({ error: 'unauthenticated' }, 401)
  return c.json({ address: session.address, email: session.email }) // [!code focus]
})
```

## Notes & Gotchas

### Cookies are issued by your origin

The session cookie is set on **your app's** origin, not the wallet's. The SDK sees `Set-Cookie` because it makes the verify call itself.

This means `/auth` and `/me` must live on the same origin as your app for the cookie to attach.

### Same-origin in development

Browsers attach `SameSite=Lax` cookies only on same-site requests. Run your auth handler on the app's origin — Vite's proxy or a colocated worker (see [`authentication`](https://github.com/tempoxyz/accounts/tree/main/examples/authentication)) is the easiest path.

### Non-browser clients

Use the object form with `returnToken: true` when the SDK can't store cookies (CLI, React Native). The verify response then returns `{ token }` for use as `Authorization: Bearer <token>`.

```ts
tempoWallet({ auth: { url: '/auth', returnToken: true } })
```

### Custom verification logic

Pass [`onAuthenticate`](/docs/server/handler.auth#onauthenticate) to gate session issuance. Throw to reject the request with a `401`, or return a `Response` to merge custom JSON onto the verify reply.

## Next Steps

<Cards>
  <Card description="Reference for the server-side auth handler — endpoints, options, getSession, SessionPayload." to="/docs/server/handler.auth" icon="lucide:server" title="Handler.auth Reference" />

  <Card description="Detailed parameters for the wallet_connect auth capability." to="/docs/rpc/wallet_connect" icon="lucide:link" title="wallet_connect Reference" />

  <Card description="Choose the right key-value store to back challenges and sessions across worker instances." to="/docs/server/kv" icon="lucide:database" title="KV Stores" />

  <Card description="Configure CSP, trusted origins, and cookie hardening before going live." to="/docs/production" icon="lucide:shield-check" title="Deploying to Production" />
</Cards>
