Tokens and sessions
Token types
| Value | Purpose | Handling |
|---|---|---|
| Authorization code | One-time token exchange | Consume once on the server |
| OAuth access token | Access to resources accepting that token | Send as Authorization: Bearer …; check route support |
| OIDC ID token | Identity assertion for the registered client | Validate signature, issuer, audience, expiry, and nonce when used |
| Refresh token | Renew authorized access | Keep server-side; replace it if a refresh returns a new token |
| Better Auth session cookie | Sign-in to a specific application's account APIs | Let Better Auth issue and validate it |
| Bitcoin auth token | Signed request proof | Verify the expected path, payload, key, and freshness for that API |
A Bitcoin signature token is not an OAuth bearer token. A Sigma OAuth access token is not automatically a Better Auth session for every account route. Your application's session and Sigma's session live on different origins.
Scopes and claims
| Scope | Purpose |
|---|---|
openid | OIDC ID token and subject identity |
profile | Profile claims, including public-key/profile information when available |
email | Verified Better Auth account email when available |
offline_access | Request refresh access |
The issuer's sub identifies the account. bap_id identifies the selected BAP profile. bap can be a JSON-encoded string containing id, rootAddress, currentAddress, and identity; do not assume a full bsv-bap instance or an idKey field. Profile fields can be missing, stale, or user-authored. An email typed into profile metadata is not verified account email.
export function parseBapClaim(value: unknown): Record<string, unknown> | null {
try {
const parsed = typeof value === "string" ? JSON.parse(value) : value;
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
? parsed : null;
} catch {
return null;
}
}Parsing a claim is not token verification. Use a maintained OIDC/JWT verifier with the configured issuer, expected client audience, and discovery JWKS. Never authorize from a decoded but unverified JWT. Userinfo can be fetched directly from the trusted issuer with a valid access token; the server exchange helper does this.
Lifetimes
The audited issuer config sets authorization codes to 600 seconds, access tokens to 3,600 seconds, and refresh tokens to 604,800 seconds. Treat actual expires_in and validated token expiry as authoritative; session expiry is configured separately. These numbers are configuration, not an SLA.
Refresh and revocation
Refresh at POST /api/auth/oauth2/token with form fields grant_type=refresh_token, refresh_token, and client_id, plus the Sigma Bitcoin X-Auth-Token client signature over the request body. Do this on your backend. See Bitcoin authentication for signing rules.
The live discovery document advertises introspection, revocation, and end-session endpoints. Use the authentication method required by the registered client and endpoint. Revoking a token, revoking an authorized app, ending the Sigma session, and signing out of your application are separate operations. A local logout alone does not erase all grants.
Storage
Prefer opaque HttpOnly application session cookies and server-side OAuth token storage. Do not place access or refresh tokens in localStorage examples. Redact tokens from logs and support reports. Better Auth's Sigma session cookie is normally __Secure-better-auth.session_token in production and better-auth.session_token in HTTP development; use the session API instead of parsing it yourself.