Skip to main content

Token Lifecycle

Ithbat IAM issues three token types on successful authentication: an access token, a refresh token, and an ID token. Understanding how these tokens work, expire, and rotate is essential to building a secure and reliable integration.


Token Types

Access Token

PropertyValue
FormatJWT (RS256)
Default expiry1 hour (OIDC_ACCESS_TOKEN_LIFESPAN)
UsageSent as Authorization: Bearer <token> with every API request

The access token is a signed JWT. It contains the user's identity claims and is verified by the API server using the tenant's public key. Your backend should validate the JWT signature on every request — do not call Ithbat IAM on each request to verify tokens.

Standard claims:

{
"sub": "usr_01J8X...",
"iss": "https://api.ithbat.io",
"aud": "https://api.ithbat.io",
"exp": 1740400800,
"iat": 1740397200,
"jti": "tok_01J8X..."
}

Custom claims (Ithbat extensions):

{
"tenant_id": "ten_01J8X...",
"email": "[email protected]",
"display_name": "Sara Al-Rashidi",
"roles": ["admin"],
"permissions": ["user:read", "user:write"],
"region": "ksa"
}

Refresh Token

PropertyValue
FormatOpaque (hashed, not a JWT)
Default expiry30 days (OIDC_REFRESH_TOKEN_LIFESPAN = 720h)
RotationSingle-use — a new refresh token is issued on every refresh

The refresh token is stored server-side as a SHA-256 hash. It cannot be decoded to reveal user information. On reuse of an already-consumed refresh token, the server revokes the entire token family (rotation with reuse detection).

ID Token

PropertyValue
FormatJWT (RS256, OIDC-compliant)
UsageClient-side identity assertions, not for API authorization

The ID token follows the OIDC specification and contains the user's profile information. Use it to display user information in your UI. Do not use it as an authorization credential for API calls.


Token Refresh Flow

When the access token expires, use the refresh token to obtain a new pair without requiring the user to log in again.

POST /api/v1/auth/refresh

Request

{
"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..."
}

Response — 200 OK

{
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "bmV3UmVmcmVzaFRva2Vu...",
"idToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 3600
}
warning

The old refresh token is immediately invalidated after a successful refresh. Store the new refreshToken from the response. Reusing the old token triggers reuse detection and revokes all sessions for the user.

sequenceDiagram
participant Client
participant API as Ithbat IAM

Client->>API: POST /api/v1/auth/refresh { refreshToken }
API->>API: Validate refresh token, check expiry
API->>API: Generate new access token + new refresh token
API->>API: Revoke old refresh token
API-->>Client: { accessToken, refreshToken, idToken, expiresIn }
Client->>Client: Store new tokens, discard old

Token Validation

Validate access tokens in your backend using the tenant's JWKS endpoint.

JWKS endpoint:

GET https://api.ithbat.io/.well-known/jwks.json

OIDC discovery:

GET https://api.ithbat.io/.well-known/openid-configuration

Most JWT libraries support automatic JWKS key rotation. Configure them to fetch keys from the JWKS endpoint rather than hardcoding the public key.

tip

Cache the JWKS response and refresh it when you encounter a key ID (kid) in a token that is not in your cache. This avoids a network call on every request while still handling key rotation.


Session Management

Each login creates a server-side session tracked in Redis. Sessions store metadata (IP address, device type, location) for security visibility and allow targeted revocation.

List My Sessions

GET /api/v1/auth/sessions

Required header: Authorization: Bearer <accessToken>

Pass X-Session-ID to identify the current session (it will be marked isCurrent: true in the response).

Response — 200 OK

[
{
"id": "sess_01J8X...",
"deviceType": "desktop",
"deviceName": "Chrome on macOS",
"ipAddress": "41.64.12.100",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...",
"location": {
"country": "SA",
"city": "Riyadh"
},
"createdAt": "2026-02-20T08:30:00Z",
"lastActivity": "2026-02-24T09:45:00Z",
"isCurrent": true
},
{
"id": "sess_01J9Y...",
"deviceType": "mobile",
"deviceName": "Safari on iPhone",
"ipAddress": "197.34.11.55",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)...",
"location": {
"country": "EG",
"city": "Cairo"
},
"createdAt": "2026-02-18T14:00:00Z",
"lastActivity": "2026-02-23T20:10:00Z",
"isCurrent": false
}
]

Revoke a Specific Session

DELETE /api/v1/auth/sessions/&#123;id&#125;

Required header: Authorization: Bearer <accessToken>

Response — 200 OK

{
"message": "Session revoked successfully"
}

Revoke All Other Sessions

Invalidates all sessions except the current one. Useful for "sign out of all other devices".

DELETE /api/v1/auth/sessions

Required header: Authorization: Bearer <accessToken>

Response — 200 OK

{
"message": "All other sessions revoked successfully"
}

Automatic Token Refresh (SDK)

The Ithbat SDK handles token refresh automatically. When a request fails with 401 Unauthorized due to an expired access token, the SDK intercepts the response, refreshes the token, and retries the original request transparently.

import { IthbatSDK } from '@ithbatiam/sdk';

const ithbat = new IthbatSDK({
tenantId: 'ten_01J8X...',
baseUrl: 'https://api.ithbat.io',
// SDK manages token storage and refresh automatically
storage: 'localStorage', // or 'memory' for server-side apps
});

// This request will transparently refresh the token if needed
const user = await ithbat.users.getMe();

Manual Refresh (without SDK)

async function refreshTokens(refreshToken) {
const resp = await fetch('https://api.ithbat.io/api/v1/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});

if (!resp.ok) {
// Refresh token is expired or invalid — redirect to login
throw new Error('Session expired');
}

const tokens = await resp.json();
// Store tokens.accessToken and tokens.refreshToken
return tokens;
}

Token Storage Best Practices

Storage optionXSS riskCSRF riskRecommendation
localStorageHigh (JS-accessible)NoneAvoid for sensitive apps
sessionStorageHigh (JS-accessible)NoneAcceptable for session-scoped SPAs
HttpOnly cookieNoneMedium (mitigate with SameSite)Preferred for web apps
In-memory (variable)None (cleared on refresh)NoneBest for SPAs with short sessions
tip

For web applications, store the access token in memory and the refresh token in an HttpOnly, Secure, SameSite=Strict cookie. The access token is short-lived (1 hour) and can be re-obtained from the refresh cookie if the page is refreshed.

danger

Never store tokens in localStorage for applications that handle sensitive data. XSS vulnerabilities can silently exfiltrate all tokens from localStorage.


Security Best Practices

Refresh Token Rotation

Ithbat IAM uses strict refresh token rotation. Each refresh invalidates the previous token and issues a new one. If your system detects that the old token was used again after rotation, all sessions for that user are immediately revoked.

Token Revocation on Password Change

When a user changes their password and sets revoke_other_sessions: true, all refresh tokens for that user are revoked. Active access tokens remain valid until their natural expiry (up to 1 hour). If you require immediate revocation, decrease OIDC_ACCESS_TOKEN_LIFESPAN.

Token Revocation on Logout

POST /api/v1/auth/logout revokes all refresh tokens and deletes all server-side sessions for the user. Active access tokens cannot be proactively revoked — they remain valid until expiry. Design your system so that access token expiry is short (15–60 minutes) if this matters to you.


Code Examples

cURL — Refresh Tokens

curl -X POST https://api.ithbat.io/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..."}'

cURL — List Sessions

curl https://api.ithbat.io/api/v1/auth/sessions \
-H "Authorization: Bearer eyJhbGci..." \
-H "X-Session-ID: sess_01J8X..."

cURL — Revoke All Other Sessions

curl -X DELETE https://api.ithbat.io/api/v1/auth/sessions \
-H "Authorization: Bearer eyJhbGci..." \
-H "X-Session-ID: sess_01J8X..."

JavaScript — Decode Access Token Claims

function decodeJwtPayload(token) {
const [, payloadB64] = token.split('.');
const json = atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/'));
return JSON.parse(json);
}

const claims = decodeJwtPayload(accessToken);
console.log(claims.sub); // usr_01J8X...
console.log(claims.tenant_id); // ten_01J8X...
console.log(claims.roles); // ['admin']
console.log(claims.permissions); // ['user:read', 'user:write']
console.log(new Date(claims.exp * 1000)); // expiry date

Error Handling

HTTP StatusError CodeCause
400VALIDATION_ERRORrefreshToken field missing
401UNAUTHORIZEDRefresh token not found (already used or revoked)
401UNAUTHORIZEDRefresh token expired (30-day TTL passed)
403FORBIDDENUser account suspended or deactivated

Next Steps

  • Email & Password — how tokens are first issued
  • MFA — how MFA affects the login and token issuance flow
  • WebAuthn / Passkeys — passwordless login that returns the same token structure
  • Social Login — social login returns identical token structures