OAuth2 Token Revocation
Revoke an access or refresh token.
Endpoint
POST /oauth2/revokeDescription
Revokes an access token or refresh token, immediately invalidating it. This endpoint follows RFC 7009: OAuth 2.0 Token Revocation.
When revoking a refresh token, the entire token family may be revoked depending on the implementation.
Authentication
Required: No (but may require client authentication in production)
Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | Must be application/x-www-form-urlencoded |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Yes | The token to revoke (access or refresh token) |
token_type_hint | string | No | Hint about token type: access_token or refresh_token |
Example Request
http
POST /oauth2/revoke HTTP/1.1
Host: localhost:4000
Content-Type: application/x-www-form-urlencoded
token=rt_x1y2z3a4b5c6d7e8f9&token_type_hint=refresh_tokenResponse
Success Response
Status Code: 200 OK
json
{}Note: Per RFC 7009, the revocation endpoint always returns 200 OK, even if the token doesn't exist or is already revoked. This prevents token scanning attacks.
Error Response
400 Bad Request - Missing Token:
json
{
"error": "invalid_request"
}Token Type Hint
The token_type_hint parameter is optional but recommended:
access_token- Token is an access token (JWT)refresh_token- Token is a refresh token (opaque)
If not provided, the server will attempt to determine the token type automatically.
Revocation Behavior
Access Token Revocation
When an access token is revoked:
- Token is marked as revoked in database (by JTI)
- Token becomes immediately invalid
- Subsequent API calls with this token will fail
- Token introspection will return
active: false
Refresh Token Revocation
When a refresh token is revoked:
- Token is marked as revoked in database
- Token can no longer be used to obtain new access tokens
- Related access tokens remain valid until expiry (stateless JWT)
- Optionally, the entire token family may be revoked
Code Examples
cURL
bash
# Revoke refresh token
curl -X POST http://localhost:4000/oauth2/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=rt_x1y2z3a4b5c6d7e8f9&token_type_hint=refresh_token"
# Revoke access token
curl -X POST http://localhost:4000/oauth2/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...&token_type_hint=access_token"JavaScript
javascript
async function revokeToken(token, tokenTypeHint = 'refresh_token') {
const response = await fetch('http://localhost:4000/oauth2/revoke', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
token,
token_type_hint: tokenTypeHint,
}),
});
// Always returns 200 OK per RFC 7009
return response.ok;
}
// Usage - Logout function
async function logout() {
const refreshToken = localStorage.getItem('refresh_token');
const accessToken = localStorage.getItem('access_token');
// Revoke refresh token
if (refreshToken) {
await revokeToken(refreshToken, 'refresh_token');
}
// Revoke access token
if (accessToken) {
await revokeToken(accessToken, 'access_token');
}
// Clear local storage
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
// Redirect to login
window.location.href = '/login';
}TypeScript
typescript
type TokenTypeHint = 'access_token' | 'refresh_token';
async function revokeToken(token: string, tokenTypeHint?: TokenTypeHint): Promise<void> {
const params = new URLSearchParams({ token });
if (tokenTypeHint) {
params.set('token_type_hint', tokenTypeHint);
}
const response = await fetch('http://localhost:4000/oauth2/revoke', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params,
});
if (!response.ok && response.status === 400) {
const error = await response.json();
throw new Error(`Token revocation failed: ${error.error}`);
}
}
// Token manager with revocation
class TokenManager {
async revokeAll(): Promise<void> {
const refreshToken = this.getRefreshToken();
const accessToken = this.getAccessToken();
try {
if (refreshToken) {
await revokeToken(refreshToken, 'refresh_token');
}
if (accessToken) {
await revokeToken(accessToken, 'access_token');
}
} finally {
// Always clear tokens locally
this.clearTokens();
}
}
private getRefreshToken(): string | null {
return localStorage.getItem('refresh_token');
}
private getAccessToken(): string | null {
return localStorage.getItem('access_token');
}
private clearTokens(): void {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
}
}Best Practices
- Logout: Always revoke tokens when user logs out
- Token Leak: Revoke tokens if you suspect they've been compromised
- Clear Local Storage: Always clear tokens from client storage after revocation
- Error Handling: Don't rely on revocation success for logout flow
- Both Tokens: Revoke both access and refresh tokens during logout
Security Considerations
- Always Success: Endpoint always returns 200 OK to prevent token scanning
- Immediate Effect: Tokens are immediately invalidated upon revocation
- Client-Side Cleanup: Always clear tokens from client storage
- Network Failure: If revocation fails, still clear tokens locally and logout user
- HTTPS Required: Use HTTPS in production to protect tokens in transit
Related Endpoints
- POST /oauth2/token - Get tokens
- POST /oauth2/introspect - Check token validity
- POST /v1/auth/logout - Session-based logout