Token Silent Refresh in Frontend Development: Concepts, Strategies, and Implementation
This article explains the fundamentals of token‑based authentication, the drawbacks of fixed token lifetimes, and presents multiple silent‑refresh techniques—including double‑token mechanisms, front‑end timers, server‑side proactive refresh, and concurrency‑safe lock strategies—along with practical code examples and performance considerations for high‑concurrency scenarios.
1. Introduction
In front‑end development, user authentication is often implemented with tokens, but their limited lifespan forces users to re‑login when they expire, harming user experience. "Token silent refresh" is a common optimization that automatically renews tokens without user interaction.
2. Token Basics and Pain Points
2.1 How Tokens Work
Tokens (e.g., JWT or OAuth2 access tokens) are issued by the server after a successful login and are sent with each request in headers or parameters. The server validates the token to confirm the user's identity and permissions.
2.2 Problems with Fixed Expiration
Problem Category
Specific Issue
Example Scenario
Poor User Experience
Frequent re‑login due to short lifetimes (e.g., 15 minutes)
User filling a long form loses data when the token expires.
Security Risks
Long‑lived tokens (e.g., 7 days) can be stolen and abused
Attacker accesses user data for a week after token theft.
High‑Concurrency Performance
Mass token expiry at the same time overloads the server
All users' tokens expire on the hour, causing a spike in refresh requests.
Dynamic Adaptation
Static lifetimes cannot adjust to user behavior or risk level
Payments need short tokens, browsing can use longer ones.
3. What Is Token Silent Refresh?
When an access token expires, the system automatically obtains a new token so the user does not notice any interruption. The goals are to improve user experience, maintain security, and optimise performance.
Application Scenarios
Single‑page web apps (Vue, React, Angular) where users may stay idle for long periods.
Mobile apps that run in the background for extended times.
Multi‑device synchronization where one device’s token expiry should not affect others.
Real‑time communication apps that require continuous server connections.
4. Technical Implementations
4.1 Double‑Token Mechanism (Access Token + Refresh Token)
The server issues a short‑lived access token and a long‑lived refresh token. When the access token expires, the client uses the refresh token to obtain a new access token.
import axios from 'axios'
const service = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000
})
// Request interceptor – add Access Token
service.interceptors.request.use(config => {
const accessToken = localStorage.getItem('access_token');
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
});
// Response interceptor – handle 401 and refresh token
service.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const accessToken = await refreshToken();
localStorage.setItem('access_token', accessToken);
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return service(originalRequest);
} catch (refreshError) {
router.push('/login');
}
}
return Promise.reject(error);
}
);
async function refreshToken() {
try {
const response = await service.get('/refresh', {
params: { token: getRefreshTokenFromCookie() },
timeout: 30000
});
return response.data.accessToken;
} catch (error) {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
throw error;
}
}
export default service;4.2 Front‑End Timed Refresh
The client proactively refreshes the token before it expires, typically one minute in advance.
let refreshTimeout;
function checkAndRefreshToken() {
const accessToken = localStorage.getItem('accessToken');
if (accessToken) {
const expiresIn = getTokenExpiresIn(accessToken);
if (expiresIn < 60) {
refreshAccessToken().then(newAccessToken => {
localStorage.setItem('accessToken', newAccessToken);
refreshTimeout = setTimeout(checkAndRefreshToken, (getTokenExpiresIn(newAccessToken) - 60) * 1000);
});
} else {
refreshTimeout = setTimeout(checkAndRefreshToken, (expiresIn - 60) * 1000);
}
}
}
checkAndRefreshToken();4.3 Server‑Side Proactive Refresh
The server checks the remaining lifetime on each request and, if it is below a threshold, returns a new access token in the response headers.
// Server middleware
app.use((req, res, next) => {
const accessToken = req.headers.authorization?.split(' ')[1];
if (accessToken) {
const expiresIn = getTokenExpiresIn(accessToken);
if (expiresIn < 60) {
const newAccessToken = generateAccessToken(req.user);
res.set('New-Access-Token', newAccessToken);
}
}
next();
});
// Client response interceptor
service.interceptors.response.use(response => {
const newAccessToken = response.headers['new-access-token'];
if (newAccessToken) {
localStorage.setItem('accessToken', newAccessToken);
}
return response;
});4.4 Double Token + Concurrency Lock
In high‑concurrency environments, a lock and request queue prevent multiple simultaneous refreshes.
// Request interceptor – add token
service.interceptors.request.use(config => {
if (config.url !== '/login') {
const accessToken = localStorage.getItem('access_token');
config.headers['Authorization'] = `Bearer ${accessToken}`;
}
if (config.url === '/refresh_token') {
const refreshToken = localStorage.getItem('refresh_token');
config.headers['Authorization'] = `Bearer ${refreshToken}`;
}
return config;
});
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) => {
failedQueue.forEach(prom => {
if (error) prom.reject(error);
else prom.resolve(token);
});
if (!error) failedQueue = [];
};
service.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
if (!isRefreshing) {
isRefreshing = true;
try {
const accessToken = await refreshToken();
localStorage.setItem('access_token', accessToken);
originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
processQueue(null, accessToken);
return service(originalRequest);
} catch (err) {
processQueue(err, null);
router.push('/login');
} finally {
isRefreshing = false;
}
} else {
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
});
}
}
return Promise.reject(error);
}
);
export default service;5. Summary
Token silent refresh improves user experience and security. Various methods—double‑token, front‑end timer, server‑side proactive refresh, and lock‑based concurrency handling—suit different scenarios, each with its own advantages and trade‑offs.
Method
Applicable Scenarios
Advantages
Disadvantages
Double Token Mechanism
General purpose
High security, low refresh frequency
Requires server support for two tokens
Front‑End Timed Refresh
Highly active users
Avoids sudden expiry during requests
Needs timer management
Double Token + Concurrency Lock
High‑concurrency environments
Solves concurrent refresh problems
Complex implementation
Server‑Side Proactive Refresh
Server‑controlled scenarios
No extra client logic needed
Increases server load
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.