au1h
Concepts

Tokens

The JWT au1h mints for your backend, and how to verify it.

Getting a token

A signed-in client calls GET /api/auth/token with its session cookie and x-app-id. The SDK does this for you (auth.getToken()), caches the result and refreshes it shortly before expiry.

What is inside

ClaimValue
subUser id
email, nameFrom the user record
applicationIdThe application's id
issau1h's base URL (BETTER_AUTH_URL)
audYour application slug
iat, expIssued at, expires. Lifetime is 15 minutes.

Signed with EdDSA (Ed25519). The public keys are at GET /api/auth/jwks, which needs no x-app-id and no authentication.

Verifying

Always check: signature against the JWKS, exp, iss = au1h URL, aud = your slug. All applications share one JWKS, so the aud check is what stops a token minted for another application from being accepted by yours. The SDKs do all four.

There is no app_id claim. Older snippets that read payload.app_id get undefined.

Revocation

JWTs are stateless and valid until exp. If you need to react to sign-out, bans or session revocation within those 15 minutes, call the introspection endpoint with your application secret, or use the gateway, which checks the live session on every request.

Manual verification (no SDK)

import { createRemoteJWKSet, jwtVerify } from "jose";

const JWKS = createRemoteJWKSet(new URL(`${AU1H_URL}/api/auth/jwks`));
const { payload } = await jwtVerify(token, JWKS, {
  algorithms: ["EdDSA"],
  issuer: AU1H_URL,
  audience: "todo",
});

On this page