Comprehensive token revocation mechanism that ensures tokens are invalidated in critical security scenarios, preventing unauthorized access even after authentication events.
Tokens are automatically revoked in the following scenarios:
- Password Change: All tokens invalidated immediately
- Email Change: All tokens invalidated, requires re-verification
- User-Initiated Logout All Devices: Manual token revocation
- Account Deletion: Permanent token revocation
- Security Incidents: Admin-triggered revocation
- Suspicious Activity: Automated detection and revocation
- Used for: password changes, email changes, logout all
- Stored in Redis with 7-day expiration
- Covers maximum JWT lifetime + buffer
- Used for: account deletion, banned users
- Stored in Redis without expiration
- Requires manual cleanup
┌─────────────────┐
│ Auth Event │
│ (Password/Email)│
└────────┬────────┘
│
▼
┌─────────────────┐
│ Revoke Tokens │
│ in Redis │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Sign Out All │
│ Sessions (SUP) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Clear Current │
│ Session Cookie │
└─────────────────┘
Functions:
revokeUserTokens(userId, reason, metadata?): Temporary revocationpermanentlyRevokeUserTokens(userId, reason, metadata?): Permanent revocationisTokenRevoked(userId): Check revocation statusgetRevocationDetails(userId): Get revocation infoclearRevocation(userId): Clear revocation (testing/admin)
Revocation Reasons:
type RevocationReason =
| 'password_change'
| 'email_change'
| 'user_initiated_logout_all'
| 'security_incident'
| 'account_deletion'
| 'suspicious_activity'
| 'admin_action';New Endpoints:
// Change password (revokes all tokens)
auth.changePassword({
currentPassword: string,
newPassword: string (min 8, max 128)
})
// Update email (revokes all tokens)
auth.updateEmail({
newEmail: string (valid email)
})
// Logout all devices
auth.logoutAllDevices()
// Get security status
auth.securityStatus()Token Revocation Check:
// After user authentication
if (user && await isTokenRevoked(user.id.toString())) {
user = null; // Reject request
}Every authenticated request checks if the user's tokens have been revoked.
Supabase Auth Events (supabase/functions/auth-events/index.ts)
Handles Supabase auth webhooks for automatic token revocation:
Events:
- user.updated → Revoke tokens on password/email change
- user.deleted → Permanent revocationEnsure Redis is configured with sufficient memory for token blacklist:
# In .env
REDIS_URL=your_redis_url
REDIS_TOKEN=your_redis_tokenOption A: Supabase Dashboard
- Go to Authentication > Webhooks
- Click "Add webhook"
- Configure:
- URL:
https://your-project.supabase.co/functions/v1/auth-events - Events:
user.updated,user.deleted - Secret: Generate and save to
.envasAUTH_WEBHOOK_SECRET
- URL:
Option B: Supabase CLI
# Deploy edge function
supabase functions deploy auth-events
# Set webhook
supabase functions create-webhook \
--url "https://your-project.supabase.co/functions/v1/auth-events" \
--events "user.updated,user.deleted"Add to your .env:
# Existing
SUPABASE_URL=your_supabase_url
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
REDIS_URL=your_redis_url
# New
AUTH_WEBHOOK_SECRET=your_webhook_secret
REDIS_TOKEN=your_redis_tokennpm test tests/token-revocation.test.tsTest Coverage:
- Password change revocation ✓
- Email change revocation ✓
- Logout all devices ✓
- Permanent revocation ✓
- Revocation status checks ✓
- All revocation reasons ✓
# 1. Login and get token
# 2. Change password
curl -X POST https://your-api.com/trpc/auth.changePassword \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"currentPassword":"old","newPassword":"newSecure123!"}'
# 3. Try using old token (should fail)
curl https://your-api.com/trpc/auth.me \
-H "Authorization: Bearer OLD_TOKEN"
# Expected: 401 Unauthorized# 1. Login and get token
# 2. Update email
curl -X POST https://your-api.com/trpc/auth.updateEmail \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"newEmail":"new@example.com"}'
# 3. Try using old token (should fail)
# Expected: 401 Unauthorized-
Token Theft After Password Change
- Attacker cannot use stolen token after victim changes password
-
Session Hijacking After Email Change
- All sessions invalidated when email is updated
-
Compromised Account Recovery
- User can revoke all sessions via "Logout All Devices"
-
Deleted Account Access
- Permanently revoked tokens cannot be used
-
Security Incident Response
- Admin can immediately revoke user tokens
-
Redis Dependency
- If Redis is down, revocation checks return
false(fail-open) - Mitigation: Use Redis cluster with high availability
- If Redis is down, revocation checks return
-
Race Conditions
- Brief window between revocation and next request
- Mitigation: Supabase also revokes sessions immediately
-
Webhook Delays
- Webhook may take seconds to process
- Mitigation: Server-side endpoints revoke immediately
-
Revocation Rate
# Check Redis for revocation count redis-cli KEYS "revoked:user:*" | wc -l
-
Failed Auth Attempts (Revoked)
- Monitor logs for
isTokenRevokedrejections - Alert on unusual spikes
- Monitor logs for
-
Webhook Processing
- Monitor Supabase function logs
- Alert on webhook failures
All revocation events are logged with structured data:
logger.info({
userId,
reason,
metadata,
requestId
}, "User tokens revoked");-
Always check revocation in auth middleware
- Done automatically in
context.ts
- Done automatically in
-
Use appropriate revocation reason
- Helps with debugging and compliance
-
Include metadata for audit trail
revokeUserTokens(userId, 'security_incident', { ip: request.ip, userAgent: request.headers['user-agent'], triggeredBy: 'admin' });
-
Handle revocation gracefully
- Clear client-side state
- Redirect to login with message
-
Use "Logout All Devices" if account compromised
- Available in profile settings
-
Change password immediately if suspicious
- Automatically revokes all tokens
-
Check security status
- Use
auth.securityStatus()endpoint
- Use
This implementation helps meet requirements for:
- OWASP ASVS: Session Management (V3)
- PCI DSS: Requirement 8.2.5 (token revocation)
- HIPAA: Access control (§164.312(a)(1))
- GDPR: Article 32 (security of processing)
Check:
- Redis connection:
await getRedis() - Webhook configured: Check Supabase dashboard
- Environment variables: Verify
REDIS_URL,AUTH_WEBHOOK_SECRET
Check:
- Revocation details:
getRevocationDetails(userId) - Redis TTL: May be set too short
- Clock skew: Ensure server time is accurate
Check:
- Supabase webhook URL is correct
- Edge function deployed:
supabase functions list - Webhook secret matches: Compare env vars
If you're adding this to an existing system:
-
Deploy token-blacklist module
git pull npm install
-
Update environment variables
# Add to .env AUTH_WEBHOOK_SECRET=generate_random_secret -
Deploy Supabase function
supabase functions deploy auth-events
-
Configure webhook
- Follow setup instructions above
-
Test in staging
npm test tests/token-revocation.test.ts -
Monitor production
- Watch logs for revocation events
- Check Redis memory usage
For issues or questions:
- Check logs:
loggeroutputs inserver/_core/token-blacklist.ts - Review tests:
tests/token-revocation.test.ts - Contact security team for incident response
Last Updated: 2025-01-23 Version: 1.0.0 Owner: Security Team