Refresh Token Rotation: Best Practices for Developers
Want to make your app more secure while keeping users happy? Refresh token rotation can help. It’s a method where refresh tokens are replaced after every use, ensuring they’re valid for one-time use only. This improves security, blocks replay attacks, and simplifies session management – all without interrupting the user experience.
Why Use Refresh Token Rotation?
- Stronger Security: Limits token misuse and provides clear activity logs.
- Better Control: Manage sessions precisely and revoke access instantly if needed.
- Smooth User Experience: Long sessions without frequent logins.
How It Works:
- When an access token expires, the refresh token is used to request a new one.
- The server issues new access and refresh tokens while invalidating the old refresh token.
- This creates a secure chain of tokens, reducing risks like token theft.
Key Steps to Implement:
- Set short access token lifetimes (15–30 minutes).
- Use single-use refresh tokens (valid for 7–14 days).
- Store tokens securely (e.g., HTTP-only cookies or secure server-side storage).
- Monitor for suspicious activity like token reuse or unusual login patterns.
By adopting refresh token rotation, you strengthen your app’s security while keeping authentication seamless for users. Ready to learn more? Let’s dive in!
Detecting session hijacking using rotating refresh tokens
How Refresh Tokens Work
This section explains the OAuth 2.0 token process and how refresh token rotation improves security.
OAuth 2.0 Token Flow
OAuth 2.0 manages refresh tokens through a defined sequence of steps. When a user logs in, the authorization server provides two tokens: a short-lived access token (valid for 15–60 minutes) and a longer-lived refresh token (lasting 7–14 days).
Here’s how the process works:
1. Initial Authentication
After a successful login, the system issues:
- A short-term access token for API calls.
- A longer-term refresh token to request new access tokens.
2. Using the Access Token
The client includes the access token in the Authorization header for each API request, like this:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
3. Refreshing the Token
When the access token expires, the client uses the refresh token to request a new one without requiring the user to log in again.
Next, let’s look at how token rotation enhances this process.
Token Rotation Process
Token rotation strengthens security by replacing tokens after every refresh, ensuring refresh tokens are valid for a single use only. Here’s how it works:
- The client notices the access token has expired.
- It sends the current refresh token to the token endpoint.
- The server validates the refresh token and issues new access and refresh tokens.
- The old refresh token is invalidated.
- The server sends the new tokens back to the client.
- The client updates its stored tokens.
This "one-time use" approach creates a secure chain of tokens, reducing the risk of misuse.
To enforce single-use refresh tokens, consider these checks:
Check | Purpose | Implementation |
---|---|---|
Token Reuse Detection | Prevent replay attacks | Track used refresh tokens in a blacklist |
Grace Period | Handle race conditions | Allow a 30-second window for concurrent requests |
Token Family Validation | Maintain token lineage | Include parent token references in new tokens |
Token rotation works seamlessly in the background, enhancing security while keeping the user experience smooth. By using this method, you ensure secure, automatic credential updates without frequent user logins.
Setting Up Token Rotation
Basic Configuration Steps
To set up token rotation, configure your authorization server with the following parameters:
- Set the access token lifetime to 15–30 minutes.
- Limit refresh token validity to a maximum of 7–14 days.
- Enable token validation checks to ensure security.
- Apply rate limiting on token endpoints to prevent abuse.
Your server should maintain a token registry with these essential fields:
Field | Purpose | Example Value |
---|---|---|
Token ID | Unique identifier | uuid-v4 |
Issue Time | Timestamp of token creation | 03/18/2025, 2:30 PM EST |
Family ID | Groups related tokens | family-uuid-v4 |
Previous Token | Tracks the parent token | prev-token-hash |
Revocation Status | Indicates token status | active/revoked |
Once configured, move on to implementing token rotation in your code.
Programming Examples
Here’s an example of token rotation using Node.js:
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
async function rotateTokens(refreshToken) {
const decodedToken = jwt.verify(refreshToken, process.env.SECRET_KEY);
// Generate new token pair
const newAccessToken = jwt.sign(
{ userId: decodedToken.userId },
process.env.SECRET_KEY,
{ expiresIn: '30m' }
);
const newRefreshToken = jwt.sign(
{
userId: decodedToken.userId,
familyId: decodedToken.familyId,
previousToken: crypto.createHash('sha256')
.update(refreshToken).digest('hex')
},
process.env.SECRET_KEY,
{ expiresIn: '7d' }
);
// Invalidate old refresh token
await invalidateToken(refreshToken);
return { accessToken: newAccessToken, refreshToken: newRefreshToken };
}
Token Storage Best Practices
After implementing token rotation, ensure tokens are stored securely by following these practices:
-
Server-side Storage
Use a secure, fast database like Redis to store token metadata. Redis’s built-in expiration support is particularly helpful:await redis.setex( `token:${tokenId}`, 604800, // 7 days in seconds JSON.stringify(tokenMetadata) );
-
Client-side Storage
For web applications, store refresh tokens in HTTP-only cookies with proper security flags:res.cookie('refreshToken', token, { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 604800000 // 7 days in milliseconds });
-
Mobile Applications
Use platform-specific secure storage options:- iOS: Keychain Services
- Android: EncryptedSharedPreferences
- React Native: Encrypted AsyncStorage
Avoid these mistakes when storing tokens:
- Never store tokens in
localStorage
, as it is vulnerable to XSS attacks. - Avoid embedding sensitive data in the JWT payload.
- Ensure all stored data is encrypted.
- Keep access and refresh tokens in separate storage locations to reduce risks.
sbb-itb-59e1987
Security Measures
Preventing Token Reuse
To stop replay attacks, monitor token usage with a centralized system that tracks token state changes:
const tokenRegistry = {
async markTokenUsed(tokenId, timestamp) {
const token = await db.tokens.findOne({ id: tokenId });
if (token.used || token.revoked) {
throw new SecurityError('Token reuse detected');
}
await db.tokens.update({
id: tokenId,
used: true,
lastUsedAt: timestamp
});
}
};
If token reuse is detected, take immediate action:
- Revoke the token to prevent further misuse.
- Log the incident for audit purposes.
- Force re-authentication for the affected session.
- Notify administrators to investigate the breach.
These steps complement the token revocation methods outlined below.
Token Revocation Steps
Token revocation can be applied at different levels depending on the situation:
Revocation Type | When to Use | Impact |
---|---|---|
Single Token | Suspicious activity on one device | Only the specific token is affected |
Family Revocation | Breach involving multiple devices | All related tokens are invalidated |
Global Revocation | Major security incident | All active tokens are revoked system-wide |
Here’s an example of family token revocation:
async function revokeTokenFamily(familyId) {
await db.tokens.updateMany(
{ familyId: familyId },
{
revoked: true,
revokedAt: new Date(),
reason: 'security_breach'
}
);
// Notify clients
await notifyClients(familyId);
// Log security event
await logSecurityEvent({
type: 'family_revocation',
familyId: familyId,
timestamp: new Date()
});
}
Usage Limits and Tracking
Monitoring token requests is essential for spotting unusual activity. Use rate limits and track usage patterns to identify potential threats:
const rateLimits = {
tokenRequests: {
window: '15m',
maxAttempts: 100,
blockDuration: '1h'
},
refreshAttempts: {
window: '24h',
maxAttempts: 1000,
blockDuration: '24h'
}
};
Key metrics to monitor include:
- Frequency of token refreshes per user
- Failed token refresh attempts
- Geographic origin of requests
- Time-based usage trends
- Number of concurrent active sessions
Set up alerts for suspicious behavior, such as:
- Multiple refresh attempts from different IPs
- Rapid token rotations
- Access during unusual hours
- Requests from unexpected locations
Store token usage data in a time-series database for better analysis and threat detection:
const metrics = {
async recordTokenUsage(tokenId, context) {
await timeseriesDB.insert({
timestamp: new Date(),
tokenId: tokenId,
userId: context.userId,
ipAddress: context.ip,
userAgent: context.userAgent,
geoLocation: await geolocate(context.ip)
});
}
};
When irregularities are detected, step up security by:
- Increasing monitoring intervals
- Shortening token expiration times
- Adding extra verification steps
- Initiating manual reviews for deeper investigation
Testing and Maintenance
Testing Procedures
Automated tests are essential for ensuring the token rotation process works as intended. Here’s an example of how to test this functionality:
describe('Token Rotation Tests', () => {
test('should rotate and validate tokens', async () => {
// Test basic rotation
const initialToken = await generateRefreshToken();
const rotatedToken = await rotateToken(initialToken);
expect(rotatedToken).not.toEqual(initialToken);
expect(await validateToken(rotatedToken)).toBeTruthy();
// Test full authentication flow
const authResponse = await authenticate(credentials);
await simulateTokenExpiry(authResponse.accessToken);
const newTokens = await performTokenRotation(authResponse.refreshToken);
await verifyTokenLineage(authResponse.refreshToken, newTokens.refreshToken);
});
});
Once you’ve confirmed that token rotation works as expected, keep an eye on system performance to identify and resolve issues early.
System Monitoring
Track the performance of token rotation using key metrics to maintain reliability:
Metric | Description | Alert Threshold |
---|---|---|
Rotation Latency | Time to complete rotation | > 500ms |
Success Rate | Successful rotations | < 99.9% |
Token Chain Length | Rotations in sequence | > 50 rotations |
Error Frequency | Failed attempts per hour | > 10 errors |
Additionally, log all token lifecycle events for better traceability:
const rotationLogger = {
async logRotationEvent(event) {
await logger.info('token_rotation', {
timestamp: new Date().toISOString(),
tokenId: event.tokenId,
rotationDuration: event.duration,
status: event.status,
errorCode: event.error || null
});
}
};
Error Management
Even with thorough testing and monitoring, errors can still occur. Use dedicated recovery mechanisms to address them effectively:
const errorHandler = {
async handleRotationError(error, context) {
// Primary error handling with integrated circuit breaker
if (this.failureCount >= 5) {
await this.activateFailover();
return;
}
switch(error.code) {
case 'TOKEN_EXPIRED':
await forceReauthentication(context.userId);
break;
case 'DATABASE_ERROR':
await this.retryWithBackoff(context);
break;
default:
await this.notifyAdministrator(error);
}
await metrics.recordError(error);
},
async retryWithBackoff(context, attempts = 0) {
if (attempts > 3) return;
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempts) * 1000));
return this.handleRotationError(context, attempts + 1);
}
};
This approach ensures errors are managed efficiently, minimizing disruptions to the system.
Conclusion
Key Takeaways
Refresh token rotation ensures a balance between security, performance, and user experience. Here are the core practices to keep in mind:
- Optimize system performance through continuous monitoring.
- Implement resilient error handling to enable smooth recovery from issues.
- Conduct rigorous testing to validate and fine-tune the rotation mechanism.
Step-by-Step Guide for Implementation
If you’re ready to implement token rotation, here’s a breakdown of the process:
-
Initial Setup
Start by building secure token storage using industry-standard encryption methods. Incorporate rate limiting and ensure your authentication server can scale to meet demand. -
Security Configuration
Define critical parameters like token lifetimes, rotation windows, and limits. For example, here’s a simple configuration:const securityConfig = { tokenLifetime: 3600, // Tokens valid for 1 hour rotationWindow: 86400, // Refresh tokens valid for 24 hours maxRotations: 30, // Maximum number of token rotations jwtAlgorithm: 'RS256', // Asymmetric encryption algorithm tokenLength: 256 // Token size in bits };
-
Monitoring Setup
Establish thresholds for system performance and set up alerts for anomalies. Be prepared to scale your infrastructure when metrics show increased demand. -
Production Deployment
Roll out the system gradually, keeping an eye on critical metrics. Maintain detailed logs of rotation events for auditing and troubleshooting. For a scalable and reliable infrastructure, consider hosting solutions such as Serverion (https://serverion.com), which supports high-performance environments.