@mrsamdev/axios-token-refresh
v0.1.0
Published
Axios plugin to handle token refresh and request queueing on authentication failures
Maintainers
Readme
@mrsamdev/axios-token-refresh
A robust Axios plugin that handles token refresh logic when API calls fail due to authentication issues. It automatically queues pending requests and retries them once a new token is obtained.
Note: This is a basic wrapper around Axios interceptors, mainly for personal use. If you're using it in production, make sure to review security, error handling, and performance aspects.
Features
- 🔄 Automatic token refresh on 401 errors
- ⏱️ Request queueing during token refresh
- 🔁 Automatic retry of queued requests after successful token refresh
- 🔧 Customizable conditions for token refresh
- 📊 Status change notifications during token refresh
- ⏰ Configurable timeout for token refresh operations
- 🔑 Customizable auth header formatting
- 🧠 Customizable request dedupe key via
getRequestKey - 🚫 Per-request refresh opt-out with
skipAuthRefresh - 🔁 Configurable refresh retry policy (
maxRetryAttempts,retryDelay) - 📦 Supports ESM and CommonJS
- 🔒 TypeScript support with full type definitions
Installation
pnpm add @mrsamdev/axios-token-refreshor
npm install @mrsamdev/axios-token-refreshor
yarn add @mrsamdev/axios-token-refreshDevelopment
- Typecheck:
pnpm typecheck - Build:
pnpm build(Vite library mode; outputs CJS/ESM + minified variants todist/) - Tests:
pnpm test(Vitest, node environment) - Coverage:
pnpm test:coverage - Library source:
src/index.ts
Usage
Basic Example
import axios from "axios";
import { createRefreshTokenPlugin } from "@mrsamdev/axios-token-refresh";
// Create an axios instance
const apiClient = axios.create({
baseURL: "https://api.example.com",
headers: {
"Content-Type": "application/json",
},
});
// Configure the refresh token plugin
const refreshPlugin = createRefreshTokenPlugin({
// Function to refresh the token
refreshTokenFn: async () => {
// Implement your token refresh logic here
const response = await axios.post("https://api.example.com/refresh-token", {
refresh_token: localStorage.getItem("refreshToken"),
});
const newToken = response.data.access_token;
localStorage.setItem("token", newToken);
return newToken;
},
// Function to get the current token
getAuthToken: () => localStorage.getItem("token"),
// Optional: Custom condition for when to refresh token
shouldRefreshToken: (error) => error.response && error.response.status === 401,
// Optional: Status change callback
onStatusChange: (status, error) => {
console.log(`Token refresh status: ${status}`);
if (error) {
console.error("Token refresh error:", error);
}
// status can be: "refreshing", "success", "failed", "error"
},
});
// Apply the plugin to your axios instance
refreshPlugin(apiClient);
// Now use your apiClient for all API calls
export default apiClient;Advanced Configuration
You can customize when token refresh is triggered with the shouldRefreshToken option:
const refreshPlugin = createRefreshTokenPlugin({
// ... other options
// Custom options added in latest version
authHeaderFormatter: (token) => `Custom ${token}`, // Default: `Bearer ${token}`
refreshTimeout: 15000, // 15 seconds timeout (default: 10000ms)
maxRetryAttempts: 3, // Total refresh attempts including the initial attempt
retryDelay: 300, // Delay in ms between refresh retry attempts
getRequestKey: (request) => `${request.method}-${request.url}-${JSON.stringify(request.data || {})}`,
shouldRefreshToken: (error) => {
// Custom logic to determine when to refresh the token
return (
// Refresh on 401 Unauthorized
(error.response && error.response.status === 401) ||
// Refresh on specific error message
(error.response && error.response.data && error.response.data.error === "token_expired") ||
// Refresh on network errors when token exists
(error.message === "Network Error" && localStorage.getItem("token"))
);
},
});Skip Refresh For Specific Requests
Use skipAuthRefresh: true to bypass refresh logic for endpoints like login/logout/refresh:
apiClient.get("/public-profile", {
skipAuthRefresh: true,
});If TypeScript complains about this custom config property, add module augmentation once in your project:
import "axios";
declare module "axios" {
interface InternalAxiosRequestConfig {
skipAuthRefresh?: boolean;
}
}TypeScript Usage
For TypeScript projects, you can take advantage of the built-in type definitions:
import axios, { AxiosError } from "axios";
import { createRefreshTokenPlugin, RefreshTokenPluginOptions } from "@mrsamdev/axios-token-refresh";
// Create plugin with full type support
const options: RefreshTokenPluginOptions = {
refreshTokenFn: async () => {
// Implementation with full type checking
return "new-token";
},
getAuthToken: () => localStorage.getItem("token"),
shouldRefreshToken: (error: AxiosError) => {
return !!error.response && error.response.status === 401;
},
};
const refreshPlugin = createRefreshTokenPlugin(options);API Reference
createRefreshTokenPlugin(options)
Creates an Axios interceptor plugin that handles token refresh.
Options
| Option | Type | Required | Default | Description |
| --------------------- | --------------------------------------------------------- | -------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| refreshTokenFn | () => Promise<string \| null> | Yes | - | Async function that refreshes the token and returns the new token. |
| getAuthToken | () => string \| null | Yes | - | Function that returns the current auth token. |
| shouldRefreshToken | (error: AxiosError, originalRequest: object) => boolean | No | Checks for 401 status or network errors | Function that determines if token refresh should be triggered. |
| onStatusChange | (status: string, error?: Error) => void | No | Console log function | Callback for token refresh status updates with error details. Status can be "refreshing", "success", "failed", or "error". |
| authHeaderFormatter | (token: string) => string | No | (token) => Bearer ${token} | Function to format the authorization header value. |
| getRequestKey | (request: AxiosRequestConfig) => string | No | method-url-params | Custom dedupe key for queued retries. Use this to include fields like data to avoid collisions. |
| refreshTimeout | number | No | 10000 (10 seconds) | Timeout for token refresh operation in milliseconds. |
| maxRetryAttempts | number | No | 1 | Number of refresh attempts before failing. Must be an integer greater than or equal to 1. |
| retryDelay | number | No | 0 | Delay in milliseconds between refresh retry attempts. Must be greater than or equal to 0. |
How It Works
- When an API call fails, the interceptor checks if the error meets the criteria for token refresh.
- If token refresh is needed, it queues the failed request and starts the token refresh process (if not already in progress).
- The refresh operation is attempted up to
maxRetryAttemptstimes with optionalretryDelaybetween attempts. - If refresh attempts fail (including timeout), all queued requests are rejected with detailed error information.
- Once the token is refreshed, all queued requests are automatically retried with the new token.
Error Handling
The plugin now provides more detailed error information when token refresh fails:
- Token refresh timeouts
- Errors in the refresh token function
- Errors in the interceptor itself
All errors are properly propagated to your application through:
- The onStatusChange callback (with error details)
- The rejected promises of pending requests
Compatibility
This plugin is compatible with Axios v0.21.0 and above.
License
MIT
