Concepts

OIDC claims

How roles, permissions, and feature flags travel inside the id_token.

OIDC claims

Auth Service injects custom claims into the id_token (and userinfo endpoint response) for every OAuth 2.1 authorization. Claims are scoped to the requesting application — two applications that share the same user pool get independent claim sets.

Available scopes

Request these scopes in the authorization URL to receive the corresponding claims:

ScopeClaim addedDescription
openidsub, iss, iat, expRequired for OIDC. Always returned.
profilename, image, updatedAtBasic user profile.
emailemail, emailVerifiedUser email address.
rolesrolesArray of role names assigned to the user in this app.
permissionspermissionsFlat array of permission strings.
featuresfeaturesJSON object of subscription plan feature flags.
offline_accessIssues a refresh_token.

Claim formats

roles

An array of role name strings:

{ "roles": ["editor", "beta-tester"] }

Roles are defined per-application via the Roles & permissions admin API.

permissions

A flat array of permission strings derived from the roles assigned to the user. The format is:

  • "resource" — read permission (action is read)
  • "resource.write" — write permission (action is write)
{ "permissions": ["articles", "comments.write", "media"] }

Permissions are derived in getUserClaims() (src/services/claims.ts): for each role the user holds in the application, all linked permissions are resolved and deduplicated.

features

A free-form JSON object from the user's active subscription plan. The shape is whatever you define when creating the plan:

{
  "features": {
    "maxProjects": 5,
    "exportEnabled": false,
    "storageGb": 10
  }
}

If the user has no active subscription for the application, features is an empty object {}.

Access gate

The customIdTokenClaims hook also enforces access control. Token issuance fails with an OIDC FORBIDDEN error if:

  • The user has no userApplications record for the requesting application, or their record is inactive.
  • The application has isMfaRequired: true and the user has not enabled any MFA method (TOTP or passkey).

This means you do not need a separate "check if user has access" API call — the token will simply not be issued.

Discovery endpoints

Auth Service exposes the following OIDC / OAuth 2.1 discovery endpoints at the issuer root (i.e. BETTER_AUTH_URL/…), in addition to the BetterAuth paths under /api/auth/…:

PathDescription
/.well-known/openid-configurationOIDC discovery document
/.well-known/oauth-authorization-serverOAuth 2.0 Authorization Server Metadata (RFC 8414)
/api/auth/jwksJSON Web Key Set (public keys for token verification)
/api/auth/oauth2/authorizeAuthorization endpoint
/api/auth/oauth2/tokenToken endpoint
/api/auth/oauth2/userinfoUserInfo endpoint
Copyright © 2026