Skip to content

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

ParameterTypeDescription
tokenstringThe 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 active
  • active: 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 claims
console.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:

Scenarioactive valueNotes
Valid tokentrueToken is current and usable
Expired tokenfalseToken’s exp has passed
Revoked tokenfalseToken was explicitly revoked
Unknown tokenfalseToken was not issued by this server
Malformed tokenfalseToken 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 tokens
const 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

ParameterTypeDescription
tokenstringThe token to revoke
tokenTypeHintstringOptional 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 logout
app.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:

CriterionLocal ValidationIntrospection
Token formatJWT onlyJWT or opaque
Network callNo (uses cached JWKS)Yes (every call)
LatencySub-millisecond10-50ms
Detects revocationNoYes
Real-time validityNo (relies on exp)Yes
Requires client credentialsNoYes
Works offlineYes (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 validation
const tokenResult = await authrim.validateToken(accessToken);
// For sensitive operations, also check with the authorization server
if (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

ErrorDescription
IntrospectionErrorFailed to call the introspection endpoint
RevocationErrorFailed to call the revocation endpoint
ClientCredentialsErrorMissing or invalid client credentials
NetworkErrorNetwork connectivity issue

Next Steps