> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endstate.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Bring your own auth

> Verify your customers with your own identity provider and give them an Endstate wallet on your domain, without a second login.

Bring your own auth (BYOA) lets you verify a customer with **your own** identity provider and give them an Endstate wallet on your domain. A customer who is already signed in to your app never signs in again for their wallet.

## What it is

By default, Endstate manages customer identity with email login. With bring your own auth, **you** own the login. When a signed-in customer needs their wallet, your backend mints a short-lived identity token; Endstate verifies it against your public keys and provisions that customer's wallet. Your identity provider stays the source of truth.

Use it when you already run authentication and want your users to get wallets on your own domain without a separate Endstate login.

## What it means

* **Your customers, your login.** They authenticate once, with you. Endstate never prompts them to sign in.
* **Wallets are scoped to your organization.** Each wallet is tied to your org and to one customer, so an external identity never resolves to a wallet outside your org.
* **It is a one-way switch.** Turning external auth on is permanent for wallet scoping.

<Warning>
  Bring your own auth is a one-way switch. Once you turn it on, every customer's
  wallet is tied to your organization, and turning it back off later will not
  change that.
</Warning>

## How it works

1. **Register your issuer once.** Tell Endstate the `issuer`, `jwks_url`, and `audience` your identity tokens carry.
2. **Mint an identity token per session.** Your backend signs a short-lived JWT for the signed-in customer.
3. **The wallet SDK exchanges it.** Your page loads the Endstate wallet with the token; the SDK verifies it and provisions the wallet.

## 1. Register your issuer

Register with your **secret key** (`end_sk_...`), server-side. This is the only credential that can change how identity is verified for your organization.

<CodeGroup>
  ```ts SDK theme={null}
  await endstate.settings.auth.update({
    issuer: "https://auth.brand.example",
    jwks_url: "https://auth.brand.example/.well-known/jwks.json",
    audience: "https://wallet.brand.example",
  });
  ```

  ```bash cURL theme={null}
  curl -X PUT https://api2.endstate.io/v1/settings/auth \
    -H "Authorization: Bearer end_sk_..." \
    -H "Content-Type: application/json" \
    -d '{
      "issuer": "https://auth.brand.example",
      "jwks_url": "https://auth.brand.example/.well-known/jwks.json",
      "audience": "https://wallet.brand.example"
    }'
  ```
</CodeGroup>

<ParamField body="issuer" type="string" required>
  The `iss` your identity tokens carry, matched exactly. Keep it stable across
  key rotation.
</ParamField>

<ParamField body="jwks_url" type="string" required>
  A public, cacheable JWKS URL that serves your signing keys. HTTPS only, and it
  must be reachable from the public internet - Endstate fetches it to verify
  your tokens.
</ParamField>

<ParamField body="audience" type="string" required>
  The `aud` your identity tokens carry, matched exactly. Use a value dedicated
  to Endstate so tokens you mint for other purposes cannot be presented here.
</ParamField>

Check the current mode any time with `endstate.settings.auth.get()`. To switch back to Endstate-managed login, see [Turn it off](#turn-it-off).

## 2. Mint an identity token

For a signed-in customer, mint a JWT signed by a key served at your `jwks_url`. It must satisfy this contract, or the exchange rejects it.

<ParamField path="alg (header)" type="RS256 | ES256" required>
  Asymmetric only. `HS*` and `none` are refused.
</ParamField>

<ParamField path="iss" type="string" required>
  Exactly the issuer you registered.
</ParamField>

<ParamField path="aud" type="string" required>
  Exactly the audience you registered.
</ParamField>

<ParamField path="sub" type="string" required>
  Your stable, opaque, unique identifier for the customer. The same customer
  must always get the same `sub` - it is how Endstate keeps their wallet
  consistent across sessions.
</ParamField>

<ParamField path="email" type="string" required>
  The customer's email. Endstate treats this as verified by you.
</ParamField>

<ParamField path="jti" type="string" required>
  A unique token id. The token is single-use: it is spent the first time it is
  presented, so mint a fresh one per exchange.
</ParamField>

<ParamField path="iat" type="number" required>
  Issued-at, Unix seconds.
</ParamField>

<ParamField path="exp" type="number" required>
  Expiry, Unix seconds. Independent of `exp`, Endstate refuses any token older
  than **300 seconds** from `iat` (with 5s clock tolerance), so mint these
  short-lived and just-in-time, right before the exchange.
</ParamField>

Example (Node, using [`jose`](https://github.com/panva/jose)):

```ts theme={null}
import { SignJWT } from "jose";

// `privateKey` corresponds to a public key served at your jwks_url (by `kid`).
const token = await new SignJWT({ email: customer.email })
  .setProtectedHeader({ alg: "RS256", kid: signingKid })
  .setIssuer("https://auth.brand.example")
  .setAudience("https://wallet.brand.example")
  .setSubject(customer.id)
  .setJti(crypto.randomUUID())
  .setIssuedAt()
  .setExpirationTime("60s")
  .sign(privateKey);
```

## 3. Hand the token to the wallet SDK

Your page loads the Endstate wallet with the browser SDK and passes it the identity token you minted. The SDK exchanges the token for a wallet session and renders the wallet. You do not call the exchange endpoint yourself.

<Note>The browser wallet SDK (`@endstate-sdk/web`) is rolling out.</Note>

## Security

* **Serve your JWKS over HTTPS and keep it reachable.** Endstate fetches it server-side to verify tokens; if it cannot be reached, verification fails closed.
* **Use a dedicated audience.** A value reserved for Endstate keeps tokens you mint for other systems from being replayed here.
* **Rotate keys, not the issuer.** Publish new keys at your `jwks_url` under a new `kid` and retire old ones there. The `issuer` and `audience` you registered should stay stable.
* **Tokens are single-use and short-lived.** Mint one per exchange, just before you need it; the 300-second ceiling bounds the window.

## Turn it off

Switch identity verification back to Endstate-managed login at any time with `endstate.settings.auth.clear()`. This affects new sign-ins only. It does not reverse wallet scoping: wallets already created for your customers stay tied to your organization.
