Skip to content

Tokens And UserInfo

The token endpoint accepts application/x-www-form-urlencoded requests. Clients are public clients and do not authenticate with a secret.

Browser-Based Clients

The authorization code may be exchanged in the browser or on a server. The /token, /token/revoke, and /userinfo endpoints, along with discovery and JWKS, return CORS headers so browser clients can call them directly.

A browser origin is allowed when it matches a registered redirect_uri, so no separate origin configuration is needed. Token and userinfo requests must not include credentials (credentials: 'include'); authenticate /userinfo with the bearer access token.

Authorization Code Exchange

http
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=client_01H...&code=...&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&code_verifier=...

Required fields:

FieldNotes
grant_typeMust be authorization_code
client_idRegistered public client ID
codeAuthorization code from the callback
redirect_uriSame redirect URI used in the authorization request
code_verifierOriginal PKCE verifier

Successful response:

json
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "...",
  "scope": "openid profile email",
  "id_token": "..."
}

The exact expires_in value is controlled by the server token configuration. scope lists the scopes the access token was issued with.

The access token is a JWT following the RFC 9068 (at+jwt) profile. Its claims are documented in Token Validation; your backend can validate it to authorize requests against your own API.

Refresh Token Grant

Use the refresh token grant to obtain a new access token after the access token expires.

http
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&client_id=client_01H...&refresh_token=...

Successful response:

json
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "...",
  "scope": "openid profile email",
  "id_token": "..."
}

Refresh tokens rotate. Store the new refresh_token from each successful refresh and discard the old one.

The refresh-token response includes a new id_token. Validate it before trusting its identity claims. Refresh-issued ID tokens do not include a nonce because no new authorization request was made.

UserInfo

Call /userinfo with a bearer access token issued to your client:

http
GET /userinfo
Authorization: Bearer eyJ...

POST /userinfo is also supported.

Successful response with all standard scopes granted:

json
{
  "sub": "user_01H...",
  "preferred_username": "alice",
  "email": "alice@example.com",
  "email_verified": true
}

Claims are scope-dependent and follow the access token's scope claim:

ScopeUserInfo fields
openidsub
profilepreferred_username
emailemail, email_verified

The access token must belong to an active client authorization. If the user revokes consent or the client is revoked, UserInfo returns an authorization error.

Revocation

Revoke a refresh token with /token/revoke:

http
POST /token/revoke
Content-Type: application/x-www-form-urlencoded

token=...&token_type_hint=refresh_token

token_type_hint is optional. Unknown, expired, or already revoked tokens are treated as successful no-ops.