OAuth 2.1 & grants
Supported grant types and how the Authorization Code, Client Credentials, and Refresh Token flows behave.
Supported grant types
| Grant type | Description |
|---|---|
| Authorization Code + PKCE (S256) | Primary flow for user-facing apps. PKCE is mandatory. |
| Client Credentials | Machine-to-machine access. Scoped to registered clients. |
| Refresh Token | Silent token renewal with rotation on each use. |
OAuth 2.1 posture
Authorization Code without PKCE is not supported — this is intentional per OAuth 2.1. Only
S256 code challenges are accepted; plain is rejected.
Authorization Code + PKCE
The primary flow for user-facing apps. The client sends a code_challenge at /api/authorize; the
user authenticates; the server issues a single-use authorization code bound to the challenge and the
exact redirect URI. At /api/token, the client proves possession with the code_verifier.
- Authorization codes are single-use and short-lived, with PKCE verifier validation (S256 only) and exact-match redirect URI validation.
- When the
openidscope is granted, the token response also includes a signedid_token. - Pass a
resourceparameter to bind the token's audience — see resource indicators.
Consent
Consent is recorded: when an authorization succeeds, the scopes actually granted are stored against the (user, client) pair, accumulating across authorizations. What that record drives today is the wording of the screen, not whether it appears.
- The consent screen is shown on every authorization by a normal user. Skipping it when
everything was already consented to is disabled for now: it also removed the only step offering
"sign in with a different account", leaving a signed-in user no way to switch.
prompt=consentis a no-op while that is the case. - The one exception is a synthetic test user on a test client, which goes straight through — the consent row is still recorded. Any other user on a test client sees the screen.
- If the request adds a scope the user has not seen before, the screen lists only the new scopes — not everything already agreed to.
- A request with no
scopemeans "everything this client is granted", and the screen lists that resolved set rather than a vague catch-all.
Scopes are described to the user with the consent copy configured on each scope.
Revoking consent
Admins can list and withdraw a user's consents from Dashboard → Users (the user side panel) or
via GET / DELETE /api/admin/users/{id}/consents — see the admin surface.
Revoking also revokes that user's refresh tokens, access tokens, and unused authorization codes for
the client, so access stops immediately rather than at token expiry.
Client Credentials
Machine-to-machine access for backend services. The client authenticates with its client_id and
client_secret and receives an access token scoped to its grants.
Public clients can't use client_credentials
A public (PKCE-only) client with token_endpoint_auth_method: none has no secret, so
client_credentials is rejected for it. See Clients.
Refresh Token (rotation)
Refresh tokens are long-lived but single-use with rotation — each refresh invalidates the old token and issues a new pair.
- Revocable (the full token plus its associated refresh chain).
- Scope is preserved through rotation.
- User environment access is re-checked on every refresh: revoking a user's access to a client's environment stops new tokens immediately rather than at the refresh token's natural expiry.