Skip to content

Instantly share code, notes, and snippets.

@bragma
Last active December 12, 2023 07:59
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bragma/f68391596de71e1bfae066be80c259dc to your computer and use it in GitHub Desktop.
Save bragma/f68391596de71e1bfae066be80c259dc to your computer and use it in GitHub Desktop.
My take on 401/token refresh axios interceptor - use promises' implicit queue to retry all pending requests awaiting on a shared promise
import axios from 'axios'
export default class ApiClient {
constructor(baseUrl, tokenStorage) {
this.http = axios.create({
baseURL: baseUrl
})
this.tokenStorage = tokenStorage
this.setupTokenInterceptors()
}
setupTokenInterceptors() {
this.setTokenInterceptor = this.http.interceptors.request.use(request => {
let token = this.tokenStorage.getAccessToken()
if (token) {
request.headers['Authorization'] = `Bearer ${token}`
}
return request
})
this.refreshTokenInterceptor = this.http.interceptors.response.use(undefined, async error => {
const response = error.response
if (response) {
if (response.status === 401 && error.config && !error.config.__isRetryRequest) {
try {
await this.tokenStorage.refreshAccessToken()
} catch (authError) {
// refreshing has failed, but report the original error, i.e. 401
return Promise.reject(error)
}
// retry the original request
error.config.__isRetryRequest = true
return this.http(error.config)
}
}
return Promise.reject(error)
})
}
}
export default class TokenService {
constructor(baseKey) {
this.accessTokenKey = `${baseKey}-access-token`
this.getAccessToken = this.getAccessToken.bind(this)
this.setAccessToken = this.setAccessToken.bind(this)
this.removeAccessToken = this.removeAccessToken.bind(this)
this.tokenRefreshHandler = null
this.refreshing = null
this.refreshAccessToken = this.refreshAccessToken.bind(this)
}
getAccessToken() {
return localStorage.getItem(this.accessTokenKey)
}
setAccessToken(accessToken) {
localStorage.setItem(this.accessTokenKey, accessToken)
}
removeAccessToken() {
localStorage.removeItem(this.accessTokenKey)
}
refreshAccessToken() {
if (!this.tokenRefreshHandler) throw new Error('No token refresh handler installed')
if (!this.refreshing) {
this.refreshing = this.tokenRefreshHandler()
this.refreshing.then(() => {
this.refreshing = null
})
}
return this.refreshing
}
}
@luispeerez
Copy link

luispeerez commented Sep 30, 2019

Do you mean if the tokenRefreshHandler uses axios to get a token an receives a 401 response? Yes, that is the case I had in mind, but looking again at the snippet, I think that token refresh request would fall here https://gist.github.com/bragma/f68391596de71e1bfae066be80c259dc#file-apiclient-js-L37
So I think you are right, it won't cause infinite loops

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment