Definition
Have you ever wondered how a resource server knows if that Bearer token it just received is still valid? Maybe it expired 5 minutes ago. Maybe the user revoked it. Maybe it was never issued by your authorization server at all. Token introspection solves this exact problem by providing a standardized way to ask “Hey, is this token legit?”
Token Introspection (RFC 7662) is an OAuth 2.0 extension that defines a protocol for resource servers or clients to query an authorization server about the current state of a token. Instead of parsing and validating tokens locally, you make an HTTP POST request to the introspection endpoint and get back metadata including whether the token is active, who it belongs to, what scopes it has, and when it expires.
This becomes critical in distributed systems where tokens might be revoked, scopes might change, or you’re dealing with opaque tokens (non-JWT tokens that don’t contain readable information). The introspection endpoint is your single source of truth for token validity, providing real-time status regardless of token format.
Example
Stripe API Gateway: When you call Stripe’s API with an access token, their API gateway doesn’t just trust the token blindly. It introspects the token with their authorization server to verify it’s still active, hasn’t been revoked, and has the required scopes for the requested operation. This happens on every request for maximum security.
Corporate API Platform: A large enterprise has 50+ microservices. When Service A receives a request with a token, it calls the central OAuth authorization server’s introspection endpoint to verify the token is active, belongs to the claimed user, and has the “read:users” scope needed for the operation.
Third-Party Integration: GitHub’s OAuth integration with CI/CD platforms. When CircleCI or Travis CI receives a webhook with an access token, they introspect it to ensure it’s still valid and the user hasn’t revoked access since the initial OAuth flow completed.
Banking API Security: A banking API receives a token for a money transfer. Before processing the transfer, it introspects the token to verify it’s active (not revoked after a password reset), still within its validity period, and has the “transfer:funds” scope with appropriate transaction limits.
Mobile App Token Validation: A mobile app backend receives tokens from potentially compromised devices. Instead of trusting JWT signatures alone, it introspects every token to detect tokens that were revoked due to suspicious activity, ensuring stolen tokens can’t be used even if they haven’t expired yet.
Analogy
The Bouncer with a Phone: Imagine JWT validation as a bouncer checking your ID card at the door - they verify the card looks real (signature), hasn’t expired, and matches your face. Token introspection is like the bouncer also calling the government database to verify your ID wasn’t just reported stolen, revoked, or flagged. The card might look valid, but only the central authority knows its current status.
The Concert Ticket Verification: You have a printed concert ticket that looks legitimate. The ticket checker doesn’t just look at it - they scan it in their system to verify it wasn’t already used, wasn’t refunded, and the concert actually exists. Token introspection is that real-time verification against the central database.
The Library Card Check: Your library card has an expiration date printed on it, but when you try to check out a book, the librarian scans it in their system. Why? Because even though the card says “valid until 2027,” you might have unpaid fines, a suspended account, or exceeded your borrowing limit. The card itself can’t tell the librarian that - only the central system can.
The Credit Card Authorization: When you swipe your credit card at a store, the card has your name and number, but the terminal doesn’t just trust that. It contacts your bank in real-time to verify the card is active, not reported stolen, has sufficient funds, and the transaction fits normal patterns. That authorization request is exactly what token introspection does for OAuth tokens.
Code Example
# POST request to introspection endpoint
POST /oauth/introspect HTTP/1.1
Host: authorization-server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
token=b9e8e4f0a1b2c3d4e5f6g7h8i9j0k1l2&
token_type_hint=access_token
# Successful response for active token
HTTP/1.1 200 OK
Content-Type: application/json
{
"active": true,
"scope": "read:users write:users read:orders",
"client_id": "s6BhdRkqt3",
"username": "jdoe",
"token_type": "Bearer",
"exp": 1672531200,
"iat": 1672527600,
"nbf": 1672527600,
"sub": "user-12345",
"aud": "https://api.example.com",
"iss": "https://auth.example.com",
"jti": "token-unique-id-abc123"
}
# Response for inactive/revoked token
HTTP/1.1 200 OK
Content-Type: application/json
{
"active": false
}
# Example: Resource server validating incoming request
# Node.js/Express example
const express = require('express');
const axios = require('axios');
app.get('/api/protected-resource', async (req, res) => {
// Extract token from Authorization header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid token' });
}
const token = authHeader.substring(7); // Remove "Bearer " prefix
try {
// Call introspection endpoint
const introspectionResponse = await axios.post(
'https://auth.example.com/oauth/introspect',
new URLSearchParams({
token: token,
token_type_hint: 'access_token'
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${Buffer.from(
`${CLIENT_ID}:${CLIENT_SECRET}`
).toString('base64')}`
}
}
);
const tokenInfo = introspectionResponse.data;
// Check if token is active
if (!tokenInfo.active) {
return res.status(401).json({
error: 'Token is not active or has been revoked'
});
}
// Check expiration (belt-and-suspenders approach)
if (tokenInfo.exp && tokenInfo.exp < Math.floor(Date.now() / 1000)) {
return res.status(401).json({ error: 'Token expired' });
}
// Check required scope
const requiredScope = 'read:users';
if (!tokenInfo.scope || !tokenInfo.scope.includes(requiredScope)) {
return res.status(403).json({
error: `Insufficient scope. Required: ${requiredScope}`
});
}
// Token is valid and has required scope
// Proceed with request, attach user info to request object
req.user = {
id: tokenInfo.sub,
username: tokenInfo.username,
scopes: tokenInfo.scope.split(' ')
};
// Handle the protected resource request
res.json({
message: 'Access granted',
user: req.user
});
} catch (error) {
console.error('Introspection failed:', error);
return res.status(500).json({
error: 'Failed to validate token'
});
}
});
Diagram
sequenceDiagram
participant Client
participant ResourceServer as Resource Server
participant AuthServer as Authorization Server
participant Database
Client->>ResourceServer: GET /api/data
Authorization: Bearer {token}
Note over ResourceServer: Extract token from
Authorization header
ResourceServer->>AuthServer: POST /introspect
token={token}
Authorization: Basic {credentials}
AuthServer->>Database: Query token status
Check revocation list
Verify expiration
Database-->>AuthServer: Token metadata
alt Token is active
AuthServer-->>ResourceServer: 200 OK
{"active": true, "scope": "...", ...}
Note over ResourceServer: Validate scopes
Check expiration
Verify audience
ResourceServer->>ResourceServer: Process request with
validated user context
ResourceServer-->>Client: 200 OK
Protected resource data
else Token is inactive/revoked
AuthServer-->>ResourceServer: 200 OK
{"active": false}
ResourceServer-->>Client: 401 Unauthorized
Token is not active
end
Security Notes
CRITICAL: Token introspection allows servers to verify tokens. Implement caching for performance.
Token Introspection Protocol:
- RFC 7662: Standard token introspection specification
- POST request: Send token to introspection endpoint
- Validation response: Receive token validity and metadata
- Client authentication: Introspection endpoint requires client auth
- HTTPS mandatory: Always use HTTPS for introspection
Introspection Response:
- active: Boolean indicating if token is valid
- scope: Space-separated list of scopes
- client_id: Client the token was issued to
- username: Username (if applicable)
- token_type: Token type (bearer, etc.)
- exp: Token expiration timestamp
- iat: Token issued-at timestamp
Performance Optimization:
- Cache responses: Cache introspection results to avoid latency
- Cache TTL: Cache for reasonable period (5-15 minutes)
- Token expiration: Use token expiration instead of cache expiry
- Async introspection: Background refresh before cache expiry
- Distributed cache: Use Redis or memcached for caching
Security Considerations:
- Slow endpoint: Introspection can be slow; cache results
- DoS vector: Introspection endpoint can be DoS target
- Rate limiting: Rate limit introspection requests
- Client authentication: Verify client identity on introspection
- Response caching: Don’t cache for longer than token lifetime
Best Practices
- Authenticate Introspection Clients: Require client credentials (Basic Auth, mTLS, or JWT bearer) to access the introspection endpoint
- Use token_type_hint: Always provide “access_token” or “refresh_token” hint to improve lookup performance
- Validate Critical Claims: Check
active,exp,scope, andaud(audience) in the response - Handle Inactive Tokens: Treat
"active": falseas unauthorized (401) and proceed no further - Cache Cautiously: Cache introspection results for 5-30 seconds max, and only for active tokens
- Rate Limit: Implement rate limiting on both client and server side
- Monitor Performance: Introspection adds latency - monitor and optimize endpoint performance
- Use for Opaque Tokens: Introspection is essential for opaque tokens (non-JWT) that don’t carry metadata
- Belt-and-Suspenders with JWT: Even with JWTs, introspect for real-time revocation status in high-security scenarios
- Don’t Leak Information: Always return 200 OK with minimal info for invalid tokens
Common Mistakes
Trusting JWT Expiration Alone: Relying only on JWT exp claim without introspection means revoked tokens remain valid until expiration. Solution: Introspect for critical operations.
No Client Authentication: Exposing introspection endpoint without authentication allows attackers to probe token validity. Solution: Require client credentials.
Caching Too Long: Caching introspection results for minutes or hours defeats the purpose of real-time validation. Solution: Cache for seconds, not minutes.
Leaking Information: Returning different error codes for “expired” vs “revoked” vs “invalid” leaks information to attackers. Solution: Return 200 OK with "active": false for all invalid tokens.
Performance Assumptions: Introspecting on every request without proper infrastructure can create bottlenecks. Solution: Architect for scale with caching and fast database queries.
Ignoring token_type_hint: Not providing the hint forces the server to check all token storage (access, refresh, etc.). Solution: Always specify the type.
No Rate Limiting: Allowing unlimited introspection requests enables DoS attacks. Solution: Implement per-client rate limits.