Refresh Token
Obtain a new access token using a refresh token.
Endpoint
http
POST /auth/refreshAuthentication
No authentication required (public endpoint).
Request Body
typescript
{
refreshToken: string; // Valid refresh token (contains user ID in payload)
}Response
Success (200 OK)
json
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Important: Both access token and refresh token are regenerated (token rotation for enhanced security).
Error Responses
401 Unauthorized - Invalid Token
json
{
"statusCode": 401,
"message": "Access denied",
"error": "Unauthorized"
}Examples
JavaScript (Fetch)
javascript
const refreshAccessToken = async () => {
const refreshToken = localStorage.getItem('refreshToken');
const response = await fetch('http://localhost:3000/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken })
});
if (!response.ok) {
// Refresh token expired or invalid, redirect to signin
window.location.href = '/signin';
return null;
}
const { accessToken, refreshToken: newRefreshToken } = await response.json();
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', newRefreshToken); // Store new refresh token
return accessToken;
};
// Automatic token refresh
const apiCall = async (url, options = {}) => {
let response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
}
});
// If 401, try refreshing token
if (response.status === 401) {
const newToken = await refreshAccessToken();
if (newToken) {
// Retry with new token
response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${newToken}`
}
});
}
}
return response;
};Implementation Details
Token Rotation Strategy
Correct Behavior (as implemented):
- Login/Register: Generate NEW access token (15min) + NEW refresh token (7 days)
- POST /auth/refresh: Generate NEW access token (15min) + NEW refresh token (7 days)
- Old refresh token is invalidated
- New refresh token extends session by another 7 days
- Access token expires: Client calls /auth/refresh to get new tokens
- Refresh token expires (after 7 days of inactivity): User must signin again
Why Token Rotation?
Modern JWT best practice uses refresh token rotation:
- Enhanced security: Each refresh invalidates the old token
- Compromise detection: If old token is reused, it indicates potential theft
- Extended sessions: Active users get rolling 7-day sessions
- Auto-logout: Inactive users (7+ days) must re-authenticate
Security Verification
typescript
// 1. Verify refresh token signature with JWT_REFRESH_SECRET
const payload = await jwtService.verifyAsync(refreshToken, {
secret: JWT_REFRESH_SECRET
});
// 2. Verify token matches stored token (single-use)
if (user.refreshToken !== refreshToken) {
throw new UnauthorizedException('Invalid refresh token');
}
// 3. Generate new tokens and update stored refresh token
const newTokens = await generateTokens(user.id, user.email, user.role);
await updateRefreshToken(user.id, newTokens.refreshToken);Best Practices
Client-Side Implementation
javascript
// Check token expiry before each request
const isTokenExpired = (token) => {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.exp * 1000 < Date.now();
};
// Refresh if needed
if (isTokenExpired(accessToken)) {
await refreshAccessToken();
}Axios Interceptor
javascript
import axios from 'axios';
axios.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const newToken = await refreshAccessToken();
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
return axios(originalRequest);
}
return Promise.reject(error);
}
);
