Authaz issues three tokens through the standard OAuth 2.0 + OIDC flows. Each has a different audience and lifetime; understanding which is which prevents most token-handling bugs.
┌─────────────────────────────────────────────────────────────┐
│ /oauth2/token returns this: │
├─────────────────────────────────────────────────────────────┤
│ access_token — for calling APIs (~1 h) │
│ id_token — proof of authentication (15 min) │
│ refresh_token — for getting new access tokens (30 d) │
└─────────────────────────────────────────────────────────────┘
Verify the signature against Authaz's public keys before trusting any claim. Authaz publishes them at:
GET https://your-app.authaz.io/.well-known/jwks.json
This returns a JWKS (JSON Web Key Set — the standard format for publishing public RSA keys). Cache the response for 1–24 hours; Authaz rotates keys infrequently and includes both old and new during the rotation window.
Most JWT libraries handle JWKS automatically — point them at the URL and they'll fetch and cache. Examples:
The ID token is proof that the user authenticated. It's also a JWT, signed the same way, but it's for your app's consumption only — never send it to your own APIs as a bearer credential.
Claims overlap with the access token (iss, sub, aud, exp, email, etc.) plus nonce (echoes the value you sent in the authorize request — verify it matches to prevent replay).
Default lifetime: 15 minutes. ID tokens are typically read once on login, then discarded.
Every refresh issues a new refresh token and invalidates the previous one. Always store the new token from each response:
const res = await fetch("https://your-app.authaz.io/oauth2/token", { ... });const { access_token, refresh_token: newRefresh, expires_in } = await res.json();session.refreshToken = newRefresh; // ← the new value, not the old onesession.accessToken = access_token;session.expiresAt = Date.now() + expires_in * 1000;
If a refresh token gets reused (you sent the old one after rotation), Authaz revokes the entire family — the user has to sign in again. This catches stolen refresh tokens.
Revoking a refresh token does not invalidate access tokens already in flight — those stay valid until their exp. For immediate session-kill, also call POST /api/v1/users/{userId}/sessions/revoke.
{ "active": false } means the token was revoked, expired, or never existed.
For JWTs, prefer local signature validation (faster, no round-trip). Use introspection when you need revocation awareness — e.g., a long-lived access token and an admin just suspended the user.
Cookies have a 4 KB limit per cookie and ~16 KB total per request. If your access tokens push past 3 KB, prune scopes or move to userinfo for non-essential claims.
Always validate iss and aud. A token issued for another application of yours technically validates against the same JWKS. Without checking these, an attacker who has tokens for App A could call App B's APIs.
Cache JWKS, but respect rotation. Most libraries do this for you; verify yours doesn't refetch on every request.
Don't compare exp against Date.now() directly without leeway. Allow ~5 seconds of clock skew between your server and Authaz.
Treat refresh tokens like passwords. Encrypt at rest, never log them, never send in URLs.