Authaz logoAuthaz
DocumentationAPI Reference
  • Get Started

    • Authaz
    • Core Concepts
    • Set up your app
    • Quickstart — cURL
  • Authentication

    • Authentication Settings
    • Signup
    • Invitations
    • Password Authentication
    • Multi-Factor Auth
    • Magic Link
    • OAuth / Social Login
    • Passkey (WebAuthn)
    • SAML SSO
    • Machine-to-Machine (M2M)
    • API Keys
  • Authorization

    • Authorization
    • Resources
    • Policies
    • Roles
    • Access Explorer
  • Tenancy

    • Multi-tenancy
    • Tenancy Customization
  • Brand & Host

    • Branding
    • Custom Domains
    • Communications & Email Templates
  • Operate

    • Users
    • Analytics
    • Audit Logs
    • Application Settings
  • SDK Quickstarts

    • Quickstart — Next.js
    • Quickstart — React SPA
    • Quickstart — Hono
    • Quickstart — .NET (Authaz.Sdk)
  • Recipes

    • Recipes & Cookbook
    • Next.js — first integration
    • Next.js — B2B SaaS (multi-tenant)
    • Hono — first integration
    • Hono — B2B SaaS (multi-tenant)
    • React SPA — first integration
    • React SPA — B2B SaaS (multi-tenant)
    • .NET — first integration
    • .NET — B2B SaaS (multi-tenant)
  • Reference

    • Tokens
    • API Reference
    • Errors & Troubleshooting
  • Documentation

    • How Authaz is Built
  1. Authaz
  2. Docs
  3. Reference
  4. Tokens

Reference

Tokens

4 min read·Updated Jun 19, 2026

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)  │
└─────────────────────────────────────────────────────────────┘

Access token#

The access token is what your application sends on every authenticated API call:

curl https://your-app.authaz.io/api/v1/users/me \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1Ni..."

It's an RS256-signed JWT. Default lifetime is 1 hour; tune via accessTokenLifetime.

Claims#

{
  "iss": "https://your-app.authaz.io"















Previous
.NET — B2B SaaS (multi-tenant)
Next
API Reference
,
"sub": "user_01h...",
"aud": "app_01h...",
"exp": 1735689600,
"iat": 1735686000,
"scope": "openid profile email invoices:read",
"client_id": "app_01h...",
"email": "you@example.com",
"email_verified": true,
"name": "Your Name",
"org_id": "0199...",
"tenant_id": "0198...",
"roles": ["member", "invoices_admin"],
"permissions": ["invoices:read", "invoices:create"]
}
ClaimMeaning
issIssuer — Authaz's URL for your application. Verify against this.
subSubject — the user's stable Authaz ID.
audAudience — the application ID. Verify against your AUTHAZ_CLIENT_ID.
expExpiration (Unix seconds). Reject expired tokens — don't trust the SDK to do it.
iatIssued-at.
scopeSpace-separated scopes the user consented to.
org_idThe organization the application belongs to. Same for every user.
tenant_idThe tenant context for this session. Empty for non-tenant flows. Always read this from the token, not from the URL.
rolesRole names assigned to the user (global + tenant-scoped).
permissionsEffective permissions — the flattened resolution of all roles + direct grants. Useful for short-circuiting checks in your backend.

Verification#

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:

// Node — jose
import { createRemoteJWKSet, jwtVerify } from "jose";
 
const jwks = createRemoteJWKSet(new URL("https://your-app.authaz.io/.well-known/jwks.json"));
 
const { payload } = await jwtVerify(accessToken, jwks, {
  issuer: "https://your-app.authaz.io",
  audience: process.env.AUTHAZ_CLIENT_ID,
});
// .NET — Microsoft.IdentityModel
services.AddAuthentication("Bearer").AddJwtBearer(opts =>
{
    opts.Authority = "https://your-app.authaz.io";
    opts.Audience = builder.Configuration["AUTHAZ:CLIENT_ID"];
    opts.TokenValidationParameters = new() {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
    };
});

Never verify against HS256 or use a shared secret — Authaz only signs with RSA. If your library default is HS256, change it.

ID token#

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.

Refresh token#

Opaque (not a JWT). Use it to get a new access token without sending the user back through Authaz Sign-In:

curl -X POST "https://your-app.authaz.io/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=rt_01h..." \
  -d "client_id=YOUR_APP_ID"

Default lifetime: 30 days. Tune via refreshTokenLifetime.

Rotation#

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 one
session.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.

Revocation#

You can revoke a refresh token (e.g., on user logout):

curl -X POST https://your-app.authaz.io/oauth2/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=rt_01h..." \
  -d "client_id=YOUR_APP_ID"

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.

Userinfo endpoint#

Standard OIDC userinfo — call with an access token to get fresh user claims:

curl https://your-app.authaz.io/oauth2/userinfo \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1Ni..."
{
  "sub": "user_01h...",
  "email": "you@example.com",
  "email_verified": true,
  "name": "Your Name",
  "picture": "https://...",
  "organizations": [
    { "organizationId": "0199...", "accessLevel": "owner" }
  ],
  "tenant_id": "0198..."
}

Use this when:

  • The access token is large and you want a slimmer payload.
  • Claims may have changed since the token was issued (e.g., display name updated) and you want fresh data.
  • Your client is browser-side and you don't want to parse JWT internals.

Introspection#

Introspect an opaque or JWT token to confirm it's still valid (and not revoked, even if exp says it isn't):

curl -X POST https://your-app.authaz.io/oauth2/introspect \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "$AUTHAZ_CLIENT_ID:$AUTHAZ_CLIENT_SECRET" \
  -d "token=eyJhbGciOiJSUzI1Ni..."
{
  "active": true,
  "sub": "user_01h...",
  "exp": 1735689600,
  "scope": "openid profile email",
  "client_id": "app_01h...",
  "token_type": "Bearer"
}

{ "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.

Token sizes#

Approximate byte sizes for budgeting:

TokenTypical size
Access (JWT, base claims + 5 roles)1.0–1.5 KB
Access (JWT, expanded with permissions claim)2–4 KB
ID1.0–1.5 KB
Refresh (opaque)~80 bytes

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.

Practical tips#

  • 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.

Next steps#

  • Authentication Settings — tune the lifetimes and scopes that show up in the tokens above.
  • API Reference — every endpoint accepts these tokens.
  • Audit Logs — every token issuance and refresh is logged.