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
| Property | Value |
|---|---|
| Format | JWT (RS256) |
| Default expiry | 1 hour (OIDC_ACCESS_TOKEN_LIFESPAN) |
| Usage | Sent 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
| Property | Value |
|---|---|
| Format | Opaque (hashed, not a JWT) |
| Default expiry | 30 days (OIDC_REFRESH_TOKEN_LIFESPAN = 720h) |
| Rotation | Single-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
| Property | Value |
|---|---|
| Format | JWT (RS256, OIDC-compliant) |
| Usage | Client-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
}
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.
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/{id}
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 option | XSS risk | CSRF risk | Recommendation |
|---|---|---|---|
localStorage | High (JS-accessible) | None | Avoid for sensitive apps |
sessionStorage | High (JS-accessible) | None | Acceptable for session-scoped SPAs |
HttpOnly cookie | None | Medium (mitigate with SameSite) | Preferred for web apps |
| In-memory (variable) | None (cleared on refresh) | None | Best for SPAs with short sessions |
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.
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 Status | Error Code | Cause |
|---|---|---|
400 | VALIDATION_ERROR | refreshToken field missing |
401 | UNAUTHORIZED | Refresh token not found (already used or revoked) |
401 | UNAUTHORIZED | Refresh token expired (30-day TTL passed) |
403 | FORBIDDEN | User 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