Concepts

OIDC claims

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

Auth Service injects custom claims into the id_token, the userinfo endpoint response and, when the access token is a JWT, the access token, 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. The application's allowedScopes must include them:

ScopeClaim addedDescription
openidsubRequired for OIDC. The ID token also carries iss, aud, iat and exp.
profilename, picture, updated_at, companyBasic user profile. Each claim is present only when the user has a value.
emailemail, email_verifiedUser email address.
phonephone_numberUser phone number (when the user has one).
rolesrolesArray of role names assigned to the user in this app.
permissionspermissionsFlat array of permission strings.
featuresfeatures, planJSON object of subscription plan feature flags, and the plan name.
wallet:self:read—Authorizes bearer access to the signed-in user's wallet read API when resource=<BETTER_AUTH_URL>.
orgorg_idOrganization ID (multi-tenant). Added to the access token only.
offline_access—Issues 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 aggregated per resource. When the user holds both read and write on the same resource, only resource.write is emitted.

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
  },
  "plan": "pro"
}

plan is the name of the plan. If the user has no active subscription for the application, or the subscription has expired, features is an empty object {} and plan is absent.

org_id

With the org scope, the access token carries org_id. A user who belongs to exactly one organization is bound to it implicitly. A user who belongs to several is redirected to /select-org during authorization to pick one. A user in no organization gets no org_id. The claim is not added to the ID token or to userinfo. See Organizations.

client_attrs

When the application has metadata, its string entries are emitted under the client_attrs claim of its tokens (ID token, access token, userinfo; also client_credentials tokens).

Access gate

Token issuance is guarded by the same checks for the ID token and for a JWT access token (so omitting the openid scope does not bypass them). It fails with a FORBIDDEN error if:

  • The user is banned.
  • The application does not exist or is inactive.
  • The user has no active userApplications record for the requesting application. If the record exists but is inactive, access is refused as revoked. If there is no record at all, a record is created automatically when the application is public or has allowRegister turned on; otherwise the token is refused.
  • The application has isMfaRequired: true (or the user is individually flagged as MFA-required) and the user has not enabled two-factor authentication (TOTP).
  • The application restricts its social providers and none of the user's linked accounts uses an allowed one.

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 OIDC / OAuth 2.1 discovery documents at the issuer root (i.e. BETTER_AUTH_URL/…). The other endpoints are served by BetterAuth under /api/auth/…:

PathDescription
/.well-known/openid-configurationOIDC discovery document (issuer root)
/.well-known/oauth-authorization-serverOAuth 2.0 Authorization Server Metadata, RFC 8414 (issuer root)
/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