
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
token-refresh
Advanced tools
Lightweight, framework-agnostic Axios token refresh helper with robust error handling and loop prevention.
npm install token-refresh
# or
yarn add token-refresh
import axios from 'axios';
import { installAuthRefresh, setAuthHeaders } from 'token-refresh';
const api = axios.create({ baseURL: '/api' });
// Token storage functions
const getTokens = async () => {
return JSON.parse(localStorage.getItem('tokens') || 'null');
};
const setTokens = async (tokens) => {
localStorage.setItem('tokens', JSON.stringify(tokens));
};
const { uninstall } = installAuthRefresh(api, {
refresh: async (refreshToken) => {
// IMPORTANT: avoid refresh loops — use a separate client OR set skipAuthRefresh: true
const res = await api.post('/auth/refresh', { refreshToken }, { skipAuthRefresh: true });
return res.data; // <- AuthTokens: { accessToken, refreshToken, ... }
},
getTokens,
setTokens,
isSessionExpired: (ctx) => {
return ctx.status === 401;
},
isRefreshFailureTerminal: (error) => {
const status = (error as any)?.response?.status ?? 0;
return status === 401;
},
header: {
name: 'Authorization', // default: 'Authorization'
format: (t) => `Bearer ${t}` // default: Bearer format
},
onBeforeRefresh: () => {
// called right before refresh starts
console.log('Refreshing tokens...');
},
onRefreshed: (tokens) => {
// called after successful refresh with new tokens
console.log('Tokens refreshed!', tokens);
},
onRefreshFailed: (error) => {
// called when refresh fails - includes the error
console.error('Refresh failed:', error);
// e.g., redirect to login
},
maxRetries: 3,
refreshTimeout: 10_000,
// Enable logging (disabled by default for library usage)
logger: {
debug: (msg) => console.log(msg),
error: (msg, err) => console.error(msg, err),
},
logout: async () => {
// optional server-side logout
await api.post('/auth/logout', {}, { skipAuthRefresh: true });
}
});
// Recommended: seed the default header before making requests
// This avoids a cold-start race where the first request might leave without a header.
const existing = await getTokens();
if (existing?.accessToken) setAuthHeaders(api, existing.accessToken);
// Clean up when needed (tests, hot reloads, etc.)
// uninstall();
import type { AuthRefreshOptions, AuthTokens } from 'token-refresh';
const options: AuthRefreshOptions = {
refresh: async (refreshToken: string): Promise<AuthTokens> => {
// Your refresh implementation
},
// ... other options
};
The library includes specific error types for better analytics and debugging:
import { RefreshTimeoutError } from 'token-refresh';
installAuthRefresh(api, {
// ... other options
onRefreshFailed: (error) => {
if (error instanceof RefreshTimeoutError) {
// Handle timeout specifically
analytics.track('auth_refresh_timeout');
} else {
// Handle other refresh failures
analytics.track('auth_refresh_failed', { error: error.message });
}
}
});
The library prevents infinite refresh loops at multiple levels:
Failed requests are queued during refresh and automatically retried with the new token.
Refresh operations have configurable timeouts to prevent hanging requests.
For APIs that don't use Authorization: Bearer <token>:
installAuthRefresh(api, {
// ... other options
header: {
name: 'XAuthToken',
format: (token) => token // no "Bearer " prefix
}
});
When a request fails with a condition that your isSessionExpired function returns true for, the library:
isSessionExpired predicate and the request was sent with an older token than the library currently holds in memory (e.g., you fixed the header or a previous refresh already updated it), the request is retried once with the current token — no refresh call is made.Parameters:
api: AxiosInstance - The Axios instance to install the interceptor onoptions: AuthRefreshOptionsReturns: { uninstall: () => void }
interface AuthRefreshOptions {
refresh: (refreshToken: string) => Promise<AuthTokens>;
getTokens: () => Promise<AuthTokens | null>;
setTokens: (tokens: AuthTokens) => Promise<void>;
isSessionExpired: (context: { status: number; data?: any; error: AxiosError }) => boolean;
isRefreshFailureTerminal: (error: unknown) => boolean;
header?: {
name?: string; // default: 'Authorization'
format?: (token: string) => string; // default: (t) => `Bearer ${t}`
};
onBeforeRefresh?: () => void;
onRefreshed?: (tokens: AuthTokens) => void;
onRefreshFailed?: (error: unknown) => void;
maxRetries?: number; // default: 3
refreshTimeout?: number; // ms, default: 10000
logger?: { debug(msg), error(msg, err?) }; // default: no-op (quiet)
logout?: () => Promise<void>;
}
Note: isSessionExpired is required. A common implementation is:
isSessionExpired: (ctx) => ctx.status === 401,
isRefreshFailureTerminal: (error) => {
// Axios example:
const status = (error as any)?.response?.status ?? 0;
return status === 401;
}
Custom error thrown when refresh operations timeout. Useful for analytics and specific error handling.
Update or remove the instance's default auth header.
installAuthRefresh(api, {
// ... other options
// Decide when an original request failure should trigger a refresh
isSessionExpired: (ctx) => {
if (ctx.status === 401) return true;
const code = ctx.data?.code;
return ctx.status === 403 && code === 'TOKEN_EXPIRED';
},
// Decide when a refresh failure means auth is terminally lost
isRefreshFailureTerminal: (error) => {
// Axios example; adapt for your HTTP client
const status = (error as any)?.response?.status ?? 0;
const code = (error as any)?.response?.data?.code;
if (status === 401) return true; // typical: invalid/expired refresh token
return (status === 400 || status === 403 || status === 409) &&
['INVALID_GRANT', 'INVALID_REFRESH_TOKEN', 'TOKEN_REVOKED', 'TOKEN_REUSE_DETECTED'].includes(code);
}
});
const publicApi = axios.create({ baseURL: '/api' });
const protectedApi = axios.create({ baseURL: '/api' });
// Only install on the protected API
installAuthRefresh(protectedApi, {
refresh: (refreshToken) => publicApi.post('/auth/refresh', { refreshToken }),
// ... other options
});
const { uninstall } = installAuthRefresh(api, options);
// In tests or during hot reloads
afterEach(() => {
uninstall();
});
The library includes full TypeScript support and augments Axios types to support the skipAuthRefresh flag:
// This is automatically available
api.get('/data', { skipAuthRefresh: true });
FAQs
A library for refreshing expired access tokens
We found that token-refresh demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.