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

# WebAuthn Adapter

The WebAuthn adapter is for apps and enterprises that want account keys bound to their own domain. Your app owns the passkey ceremony and the Tempo Accounts SDK plugs those signatures into Tempo account flows.

Use it when you want:

* Passkeys created on your own origin.
* Your app to own registration and authentication UX.
* Tempo account features without using the hosted Tempo Wallet surface.

## Usage

:::code-group

```tsx twoslash [Web (Wagmi)]
import { createConfig, http } from 'wagmi'
import { tempo } from 'wagmi/chains'
import { webAuthn } from 'wagmi/tempo'

export const config = createConfig({
  chains: [tempo],
  connectors: [webAuthn({ authUrl: '/auth' })],
  transports: {
    [tempo.id]: http(),
  },
})
```

```ts twoslash [Web (Vanilla)]
import { Provider, webAuthn } from 'accounts'

const provider = Provider.create({
  adapter: webAuthn({ auth: '/auth' }),
})
```

```ts twoslash [CLI]
import { Provider } from 'accounts/cli'

const provider = Provider.create()
```

:::

## How Passkeys Work

WebAuthn has two ceremonies: registration creates a new credential, and authentication proves that the same credential is present later. In both cases, your server sends a challenge, the browser asks the authenticator to sign it, and your server verifies the signed response with the stored public key. The browser mediates the UI, so your app never handles the raw private key material directly.

Passkeys can live on a platform authenticator such as Face ID, Touch ID, or Windows Hello, or on a roaming authenticator such as a hardware security key. The private key stays on the device or credential provider and is not exported through the WebAuthn API.

:::info
Use `webAuthn({ auth: '/auth' })` when you want the browser to talk to a server backed ceremony. If you need to plug in your own ceremony implementation, use `WebAuthnCeremony.server({ url })` or `WebAuthnCeremony.local({ storage })`.
:::

## Setting up your Server

For a server backed flow, mount [`Handler.webAuthn`](/docs/server/handler.webAuthn) and point the client adapter at the same path. The handler exposes `POST /auth/register/options`, `POST /auth/register`, `POST /auth/login/options`, `POST /auth/login`, and `POST /auth/logout`. Use a persistent [`Kv`](/docs/server/kv) store in production so challenges, credentials, and sessions survive deploys and restarts.

:::code-group

```ts twoslash [Server]
import { Handler, Kv } from 'accounts/server'

export const handler = Handler.webAuthn({
  kv: Kv.memory(),
  origin: 'https://app.example.com',
  rpId: 'example.com',
})

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
```

```ts twoslash [Client]
import { Provider, webAuthn } from 'accounts'

const provider = Provider.create({
  adapter: webAuthn({ auth: '/auth' }),
})
```

:::

:::warning
`Kv.memory()` is fine for local development and tests, but not for production. Use a persistent `Kv` adapter instead. [Learn more](/docs/server/kv).
:::

## Learn More

### Origin and RP ID

WebAuthn credentials are scoped to an origin and an RP ID. `origin` is the full browser origin that must match during verification, while `rpId` is the effective domain the credential is bound to. In development, `http://localhost` works because browsers allow WebAuthn on localhost as a secure context exception.

Choose `rpId` carefully before you ship. If you register with `rpId: 'example.com'`, that credential can be used from `app.example.com` and other subdomains that satisfy WebAuthn's RP rules. But the reverse is not true: a passkey created with `rpId: 'app.example.com'` cannot later be used with `rpId: 'example.com'`.

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

const handler = Handler.webAuthn({
  kv: Kv.memory(),
  origin: 'https://app.example.com',
  rpId: 'example.com',
})
```

:::warning
Treat `rpId` like part of your public API. Changing it later can strand existing credentials and force users to register new passkeys.
:::

### Sessions

A successful login automatically provisions a session for the authenticated user. By default, that session is issued as a cookie and downstream routes can read it without any extra plumbing. For non-browser callers, the login response can also return a `{ token }` body so the same session can be carried as a bearer token.

For downstream routes on the same server, call `handler.getSession(req)` to resolve the current WebAuthn session. It reads the session from the cookie or from an `Authorization: Bearer <token>` header and returns `undefined` when the request is not authenticated.

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

const handler = Handler.webAuthn({
  kv: Kv.memory(),
  origin: 'https://app.example.com',
  rpId: 'example.com',
})

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

:::info
You can disable automatic session provisioning by passing `session: false` to `Handler.webAuthn`. With sessions disabled, login no longer issues a cookie or a token, the `/auth/logout` endpoint is not mounted, and `getSession` always returns `undefined`.

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

export const handler = Handler.webAuthn({
  kv: Kv.memory(),
  origin: 'https://app.example.com',
  rpId: 'example.com',
  session: false, // [!code focus]
})
```

:::

### Account Recovery

Passkeys are bound to authenticators, so recovery is different from password reset. Some credentials sync through OS or password manager providers such as iCloud Keychain, Google Password Manager, or 1Password, but you should not assume every user has sync enabled or access to the same provider on every device. A user can also lose all registered authenticators at once.

Plan for multiple credentials per user from the start. Let users add another passkey from a second device or a hardware key after they sign in, and make that visible in account settings. If losing every authenticator would lock a user out forever, you need a separate recovery flow with stronger checks and explicit support tooling.

:::warning
Do not treat possession of one device as permanent account recovery. Recovery should be deliberate, audited, and separate from normal login.
:::

See the [WebAuthn production checklist](/docs/production?adapter=web-authn) before shipping.

## Next Steps

<Cards>
  <Card description="Set up the full passkey authentication flow end to end with the WebAuthn adapter." icon="lucide:lock-keyhole" to="/docs/guides/authentication" title="Authentication" />

  <Card description="Full API reference for the webAuthn client adapter, including ceremony and auth options." icon="lucide:book" to="/docs/api/webAuthn" title="webAuthn reference" />

  <Card description="Server-side WebAuthn ceremony handler reference for registration, login, and sessions." icon="lucide:server" to="/docs/server/handler.webAuthn" title="Handler.webAuthn reference" />
</Cards>
