Refresh Token
Overviewβ
Refresh JWT access tokens without requiring the user to re-authenticate. This extends user sessions securely while maintaining security.
Purposeβ
JWT access tokens have short expiration times (typically 15-60 minutes) for security. Refresh tokens allow:
- Seamless token renewal without user interaction
- Longer session duration while maintaining security
- Ability to revoke access by invalidating refresh tokens
Endpointβ
POST /api/Auth/RefreshToken
Request Parametersβ
| Parameter | Type | Required | Description |
|---|---|---|---|
refreshToken | string | Yes | The refresh token received during login |
Requestβ
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Responseβ
Successful Refreshβ
{
"status": "success",
"message": "Token refreshed successfully",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600
}
}
Invalid Refresh Tokenβ
{
"status": "error",
"message": "Invalid or expired refresh token",
"errorCode": "AUTH_010"
}
Token Lifecycleβ
Implementation Examplesβ
React with Axios Interceptorβ
import axios from 'axios';
// Create axios instance
const api = axios.create({
baseURL: 'https://api.banklingo.com'
});
// Request interceptor: Add token to headers
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('jwt_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor: Handle token refresh
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// If 401 and not already retried
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refresh_token');
const response = await axios.post('/api/Auth/RefreshToken', {
refreshToken
});
const { token, refreshToken: newRefreshToken } = response.data.data;
// Update stored tokens
localStorage.setItem('jwt_token', token);
localStorage.setItem('refresh_token', newRefreshToken);
// Retry original request with new token
originalRequest.headers.Authorization = `Bearer ${token}`;
return api(originalRequest);
} catch (refreshError) {
// Refresh failed, redirect to login
localStorage.clear();
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
export default api;
C# HttpClientβ
Code Removed
Implementation details removed for security.
Contact support for implementation guidance.
JavaScript Fetch APIβ
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async fetch(url, options = {}) {
// Add token to headers
const token = localStorage.getItem('jwt_token');
const headers = {
'Content-Type': 'application/json',
...options.headers
};
if (token) {
headers.Authorization = `Bearer ${token}`;
}
let response = await fetch(`${this.baseUrl}${url}`, {
...options,
headers
});
// If 401, try to refresh
if (response.status === 401 && !options._retry) {
const newToken = await this.refreshToken();
if (newToken) {
// Retry with new token
headers.Authorization = `Bearer ${newToken}`;
response = await fetch(`${this.baseUrl}${url}`, {
...options,
headers,
_retry: true
});
} else {
// Refresh failed, redirect to login
window.location.href = '/login';
throw new Error('Session expired');
}
}
return response;
}
async refreshToken() {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
return null;
}
try {
const response = await fetch(`${this.baseUrl}/api/Auth/RefreshToken`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken })
});
if (response.ok) {
const result = await response.json();
const { token, refreshToken: newRefreshToken } = result.data;
localStorage.setItem('jwt_token', token);
localStorage.setItem('refresh_token', newRefreshToken);
return token;
}
} catch (error) {
console.error('Token refresh failed:', error);
}
// Refresh failed, clear tokens
localStorage.removeItem('jwt_token');
localStorage.removeItem('refresh_token');
return null;
}
}
// Usage
const api = new ApiClient('https://api.banklingo.com');
const response = await api.fetch('/api/Accounts/List');
Token Storageβ
Recommended: HttpOnly Cookiesβ
// Server sets HttpOnly cookie (most secure)
// JavaScript cannot access the token
// Automatically sent with requests
// Protected from XSS attacks
Alternative: Local Storageβ
// Store tokens in localStorage
localStorage.setItem('jwt_token', token);
localStorage.setItem('refresh_token', refreshToken);
// Retrieve tokens
const token = localStorage.getItem('jwt_token');
const refreshToken = localStorage.getItem('refresh_token');
// Clear on logout
localStorage.removeItem('jwt_token');
localStorage.removeItem('refresh_token');
Security Comparisonβ
| Storage | XSS Protection | CSRF Protection | Best For |
|---|---|---|---|
| HttpOnly Cookie | Γ’Εβ¦ Yes | Γ’Ε‘Β Γ―ΒΈΒ Requires CSRF token | Web apps |
| LocalStorage | Γ’ΒΕ No | Γ’Εβ¦ Yes | SPAs with strict CSP |
| SessionStorage | Γ’ΒΕ No | Γ’Εβ¦ Yes | Single-tab apps |
| Memory only | Γ’Εβ¦ Yes | Γ’Εβ¦ Yes | High security apps |
Token Expiration Timesβ
| Token Type | Typical Duration | Purpose |
|---|---|---|
| Access Token | 15-60 minutes | API authentication |
| Refresh Token | 7-30 days | Token renewal |
| Setup Token | 5-10 minutes | Authenticator setup |
Error Responsesβ
| Status | Error Code | Description |
|---|---|---|
| 401 | AUTH_010 | Invalid refresh token |
| 401 | AUTH_011 | Refresh token expired |
| 403 | AUTH_012 | Refresh token revoked |
| 400 | AUTH_013 | Missing refresh token |
Best Practicesβ
Securityβ
- Short access token expiration (15-60 minutes)
- Store refresh tokens securely (HttpOnly cookies preferred)
- Rotate refresh tokens on each refresh
- Revoke refresh tokens on logout
- Monitor token usage for anomalies
User Experienceβ
- Silent refresh: Refresh automatically before expiration
- Graceful degradation: Handle refresh failures elegantly
- Clear communication: Show login prompt when refresh fails
- Preserve state: Retain user's work during refresh
Implementationβ
- Use interceptors: Handle refresh automatically
- Queue requests: Prevent multiple simultaneous refresh attempts
- Retry failed requests: After successful refresh
- Clear tokens on logout: Prevent token reuse