Introspection & Revocation
Overview
While JWT access tokens can be validated locally using public keys, some scenarios require communicating with the authorization server:
- Token Introspection (RFC 7662) — Ask the authorization server whether a token is currently active
- Token Revocation (RFC 7009) — Tell the authorization server to invalidate a token
Both features require clientCredentials (a confidential client) because the authorization server authenticates the caller.
Configuration
To use introspection or revocation, configure the endpoints and client credentials:
import { AuthrimServer } from '@authrim/server';
const authrim = new AuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',
// Introspection endpoint introspectionEndpoint: 'https://auth.example.com/oauth/introspect',
// Revocation endpoint revocationEndpoint: 'https://auth.example.com/oauth/revoke',
// Required for both introspection and revocation clientCredentials: { clientId: 'my-resource-server', clientSecret: 'resource-server-secret', },});
await authrim.init();The SDK uses HTTP Basic authentication with the client credentials when calling these endpoints.
Token Introspection (RFC 7662)
Token introspection allows you to query the authorization server for the current status of a token. This is especially useful for:
- Opaque tokens — Tokens that are not self-contained JWTs and cannot be validated locally
- Real-time validity checks — Detecting revoked or expired tokens immediately
- Token metadata — Retrieving claims that may not be in the JWT
introspect API
const response = await authrim.introspect(token);Parameters
| Parameter | Type | Description |
|---|---|---|
token | string | The access token to introspect |
IntrospectionResponse
interface IntrospectionResponse { active: boolean; sub?: string; scope?: string; client_id?: string; username?: string; token_type?: string; exp?: number; iat?: number; nbf?: number; aud?: string | string[]; iss?: string; jti?: string; [key: string]: unknown; // Additional claims}The most important field is active:
active: true— The token is valid and currently activeactive: false— The token is invalid, expired, revoked, or unknown
Usage Example
const response = await authrim.introspect(accessToken);
if (!response.active) { // Token is not valid — reject the request return res.status(401).json({ error: 'invalid_token' });}
// Token is active — use the claimsconsole.log('Subject:', response.sub);console.log('Scopes:', response.scope);console.log('Client:', response.client_id);Active vs Inactive Handling
The introspection response active: false covers multiple scenarios:
| Scenario | active value | Notes |
|---|---|---|
| Valid token | true | Token is current and usable |
| Expired token | false | Token’s exp has passed |
| Revoked token | false | Token was explicitly revoked |
| Unknown token | false | Token was not issued by this server |
| Malformed token | false | Token cannot be parsed |
Introspection with Scope Check
const response = await authrim.introspect(accessToken);
if (!response.active) { return res.status(401).json({ error: 'invalid_token' });}
// Check scopes manually for introspected tokensconst grantedScopes = (response.scope ?? '').split(' ');const requiredScopes = ['read:orders', 'write:orders'];
const hasAllScopes = requiredScopes.every( (scope) => grantedScopes.includes(scope));
if (!hasAllScopes) { return res.status(403).json({ error: 'insufficient_scope' });}Token Revocation (RFC 7009)
Token revocation tells the authorization server to invalidate a specific token. After revocation, the token will no longer be accepted by the authorization server or by resource servers that use introspection.
revoke API
await authrim.revoke(token, tokenTypeHint?);Parameters
| Parameter | Type | Description |
|---|---|---|
token | string | The token to revoke |
tokenTypeHint | string | Optional hint: 'access_token' or 'refresh_token' |
Usage Example
try { await authrim.revoke(accessToken, 'access_token'); console.log('Token revoked successfully');} catch (error) { console.error('Revocation failed:', error.message);}Revocation Use Cases
- Logout — Revoke the user’s access token when they sign out
- Security incident — Immediately invalidate a compromised token
- Permission change — Revoke tokens when a user’s permissions are updated (force re-authentication)
// Example: Revoke on logoutapp.post('/api/logout', auth(), async (req, res) => { const token = req.authrim!.token.token;
// Revoke the access token at the authorization server await authrim.revoke(token, 'access_token');
res.json({ message: 'Logged out' });});Local Validation vs Introspection
Choosing between local JWT validation and token introspection depends on your requirements:
| Criterion | Local Validation | Introspection |
|---|---|---|
| Token format | JWT only | JWT or opaque |
| Network call | No (uses cached JWKS) | Yes (every call) |
| Latency | Sub-millisecond | 10-50ms |
| Detects revocation | No | Yes |
| Real-time validity | No (relies on exp) | Yes |
| Requires client credentials | No | Yes |
| Works offline | Yes (after JWKS cached) | No |
Decision Guide
flowchart TD
A["Token received"] --> B{"Token format?"}
B -->|JWT| C{"Need real-time
revocation check?"}
B -->|Opaque| D["Use Introspection"]
C -->|Yes| E["Use Introspection"]
C -->|No| F{"Sensitive
operation?"}
F -->|Yes| G["Use Both:
Local + Introspection"]
F -->|No| H["Use Local Validation"]
Hybrid Approach
For sensitive operations, combine local validation with introspection:
// Fast path: Local JWT validationconst tokenResult = await authrim.validateToken(accessToken);
// For sensitive operations, also check with the authorization serverif (isSensitiveOperation(req.path)) { const introspectionResult = await authrim.introspect(accessToken); if (!introspectionResult.active) { return res.status(401).json({ error: 'invalid_token', error_description: 'Token has been revoked', }); }}This gives you the best of both worlds:
- Fast validation for most requests (local JWT verification)
- Real-time validity for critical operations (introspection)
Caching Introspection Results
For high-throughput APIs, you can cache introspection results for a short period:
const INTROSPECTION_CACHE_TTL = 30_000; // 30 seconds
async function introspectWithCache(token: string) { const cacheKey = `introspect:${hashToken(token)}`;
// Check cache first const cached = await cache.get(cacheKey); if (cached) return cached;
// Call authorization server const result = await authrim.introspect(token);
// Cache the result (short TTL) if (result.active) { await cache.set(cacheKey, result, INTROSPECTION_CACHE_TTL); }
return result;}Error Types
| Error | Description |
|---|---|
IntrospectionError | Failed to call the introspection endpoint |
RevocationError | Failed to call the revocation endpoint |
ClientCredentialsError | Missing or invalid client credentials |
NetworkError | Network connectivity issue |
Next Steps
- Token Validation — Local JWT validation pipeline
- DPoP Validation — Sender-constrained token verification
- JWKS Management — Key caching and rotation
- Security Considerations — Production security checklist