Introspection & Revocation
Overview
Section titled “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
Section titled “Configuration”To use introspection or revocation, configure the endpoints and client credentials:
import { createAuthrimServer } from '@authrim/server';
const authrim = createAuthrimServer({ 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)
Section titled “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
Section titled “introspect API”const response = await authrim.introspect(token, tokenTypeHint?, issuerOptions?);Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
token | string | The access token to introspect |
tokenTypeHint | 'access_token' | 'refresh_token' | 'device_secret' | Optional token type hint |
issuerOptions | { issuer?: string; token?: ValidatedToken } | Optional issuer selector for multi-issuer deployments |
IntrospectionResponse
Section titled “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; cnf?: { jkt?: string }; installation_id?: string; app_display_name?: string; platform?: string; display_name?: string; fallback_display_name?: 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
Section titled “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
Section titled “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
Section titled “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)
Section titled “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
Section titled “revoke API”await authrim.revoke(token, tokenTypeHint?, issuerOptions?);Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
token | string | The token to revoke |
tokenTypeHint | 'access_token' | 'refresh_token' | 'device_secret' | Optional token type hint |
issuerOptions | { issuer?: string; token?: ValidatedToken } | Optional issuer selector for multi-issuer deployments |
Usage Example
Section titled “Usage Example”try {await authrim.revoke(accessToken, 'access_token'); console.log('Token revoked successfully');} catch (error) { console.error('Revocation failed:', error.message);}Native SSO device_secret
Section titled “Native SSO device_secret”Native SSO device_secret values use the same introspection and revocation endpoints with the device_secret token type hint. Prefer the dedicated helpers so the hint is always correct:
const response = await authrim.introspectDeviceSecret(deviceSecret, { issuer: 'https://auth.example.com',});
if (response.active) { console.log(response.installation_id); console.log(response.platform);}
await authrim.revokeDeviceSecret(deviceSecret, { issuer: 'https://auth.example.com',});Avoid logging raw device_secret values. Treat them like refresh-token grade credentials.
Revocation Use Cases
Section titled “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
Section titled “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 | Local crypto and cache lookup | Authorization-server round trip |
| 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
Section titled “Decision Guide”flowchart TD
A["Token received"] --> B{"Token format?"}
B -->|JWT| C{"Need real-time<br>revocation check?"}
B -->|Opaque| D["Use Introspection"]
C -->|Yes| E["Use Introspection"]
C -->|No| F{"Sensitive<br>operation?"}
F -->|Yes| G["Use Both:<br>Local + Introspection"]
F -->|No| H["Use Local Validation"]
Hybrid Approach
Section titled “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
Section titled “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
Section titled “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
Section titled “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