Skip to content

Instantly share code, notes, and snippets.

@hoangtrung99
Last active April 10, 2025 06:02
Show Gist options
  • Save hoangtrung99/535cff27378fc4c36cd7f48d386d1bf1 to your computer and use it in GitHub Desktop.
Save hoangtrung99/535cff27378fc4c36cd7f48d386d1bf1 to your computer and use it in GitHub Desktop.
axios instance handle refresh token
import axios, { type AxiosError, type InternalAxiosRequestConfig, type AxiosResponse } from 'axios'
/**
* Create axios instance with default configuration
*/
const request = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 5000
})
/**
* Get access token from store or session
* @returns access token or null if not found
*/
const getToken = async () => {
// TODO: Get token from your store or local storage
return 'token'
}
/**
* Get new access token using refresh token
* @returns new access token
*/
const getNewAccessToken = async () => {
// TODO: Implement refresh token logic
// Example: Call refresh token API or use your auth library
return 'new_token'
}
/**
* Interface for Promises in the queue during token refresh
*/
interface QueueItem {
resolve: (token: string) => void
reject: (error: Error) => void
}
// Token refresh state
let isRefreshing = false
let pendingRequests: QueueItem[] = []
/**
* Process pending requests after token refresh
* @param error Error if token refresh failed
* @param token New token if refresh succeeded
*/
const processPendingRequests = (error: Error | null, token: string | null = null) => {
pendingRequests.forEach(request => {
if (error) {
request.reject(error)
} else if (token) {
request.resolve(token)
}
})
// Clear all pending requests after processing
pendingRequests = []
}
/**
* Register a request to wait for a new token
* This function creates a Promise that will be resolved when a new token is available
* or rejected if token refresh fails
* @returns Promise that resolves with the new token
*/
const waitForNewToken = (): Promise<string> => {
return new Promise<string>((resolve, reject) => {
// Add callbacks to the pending queue
// These will be called by processPendingRequests when token refresh completes
pendingRequests.push({
// Will be called with the new token when refresh succeeds
resolve: (token: string) => resolve(token),
// Will be called with the error when refresh fails
reject: (error: Error) => reject(error)
})
})
}
/**
* Retry a failed request with a new token
* @param originalConfig The original request configuration
* @param token The new access token
* @returns A Promise with the retry request
*/
const retryRequestWithToken = (
originalConfig: CustomInternalAxiosRequestConfig,
token: string
): Promise<AxiosResponse> => {
if (originalConfig.headers) {
originalConfig.headers.Authorization = `Bearer ${token}`
}
return request(originalConfig)
}
/**
* Interceptor to add token to header of each request
*/
request.interceptors.request.use(async (config) => {
const token = await getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
/**
* Extend axios config to add _retry field
*/
interface CustomInternalAxiosRequestConfig extends InternalAxiosRequestConfig {
_retry?: boolean
}
/**
* Response interceptor, including token refresh handling when receiving 401 error
*/
request.interceptors.response.use(
// Return normal response if no error
(response) => response,
// Error handling
async (error: AxiosError) => {
const originalRequest = error.config as
| CustomInternalAxiosRequestConfig
| undefined
// Check if 401 (Unauthorized) error and request can be retried
const isUnauthorizedError = error.response?.status === 401
const canRetryRequest = originalRequest && !originalRequest._retry
if (isUnauthorizedError && canRetryRequest) {
// If already refreshing token (another request is handling it)
if (isRefreshing) {
try {
// Wait for the active refresh process to complete
// This Promise will be resolved when processPendingRequests is called
const newToken = await waitForNewToken()
// When we get here, the token has been refreshed by another request
// Now we can retry our original request with the new token
return retryRequestWithToken(originalRequest, newToken)
} catch (err) {
// Token refresh failed, propagate the error
return Promise.reject(err)
}
}
// Mark this request as retried and start refresh process
originalRequest._retry = true
isRefreshing = true
try {
// TODO: Get new token using refresh token
const newToken = await getNewAccessToken()
if (!newToken) {
throw new Error('Failed to refresh token')
}
// TODO: Save new token to your store
// Example: saveTokenToStore(newToken)
// Process all pending requests with new token
// This will resolve all the waitForNewToken() promises with the new token
processPendingRequests(null, newToken)
// Retry original request with new token
return retryRequestWithToken(originalRequest, newToken)
} catch (refreshError) {
// Convert error if needed
const error =
refreshError instanceof Error
? refreshError
: new Error('Unknown error when refreshing token')
// Notify all pending requests of the error
// This will reject all the waitForNewToken() promises with the error
processPendingRequests(error, null)
// TODO: Handle logout
// Example:
// setAuthState({ isAuthenticated: false })
// await signOut()
return Promise.reject(refreshError)
} finally {
// Mark refresh process as completed
isRefreshing = false
}
}
// Return original error if not 401 or can't retry
return Promise.reject(error)
}
)
export default request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment