Parallel refresh token handler using RxJS and Axios
npm install axios-stream-auth-refreshaxios-stream-auth-refresh uses RxJS BehaviorSubject to intelligently queue failed requests and refresh the token only once:
bash
npm install axios-stream-auth-refresh rxjs axios
`
Or with other package managers:
`bash
yarn add axios-stream-auth-refresh rxjs axios
pnpm add axios-stream-auth-refresh rxjs axios
bun add axios-stream-auth-refresh rxjs axios
`
$3
- axios >= 1.0.0
- rxjs >= 7.0.0
---
Quick Start
`typescript
import axios from 'axios'
import { createStreamRefreshInterceptor } from 'axios-stream-auth-refresh'
const api = axios.create({
baseURL: 'https://api.example.com',
})
createStreamRefreshInterceptor(api, async (failedRequest) => {
const { data } = await axios.post('/auth/refresh', {
refreshToken: localStorage.getItem('refreshToken'),
})
localStorage.setItem('accessToken', data.accessToken)
failedRequest.headers.Authorization = Bearer ${data.accessToken}
return true
})
api.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken')
if (token) {
config.headers.Authorization = Bearer ${token}
}
return config
})
`
---
How It Works
`
Multiple requests fail (401) ──┐
│
├──► First request triggers refresh
│ │
Other requests are queued ──────┤ │
│ ▼
│ Refresh token API call
│ │
│ ▼
│ Token updated
│ │
└────┴──► All queued requests retry
with new token
`
---
API Reference
$3
Sets up the refresh token interceptor on an Axios instance.
#### Parameters
| Parameter | Type | Required | Description |
| ----------------- | -------------------------------------------------- | -------- | --------------------------------------------------------------------- |
| instance | AxiosInstance | ✅ | The Axios instance to attach the interceptor to |
| refreshAuthCall | (config: AxiosRequestConfig) => Promise | ✅ | Async function that refreshes the token and returns true on success |
| options | AxiosStreamRefreshOptions | ❌ | Configuration options |
#### Options
`typescript
interface AxiosStreamRefreshOptions {
statusCodes?: number[]
}
`
Default: { statusCodes: [401] }
#### Request Config Extensions
`typescript
interface AxiosRequestConfig {
skipAuthRefresh?: boolean
retry?: boolean
}
`
---
Examples
$3
`typescript
import axios from 'axios'
import { createStreamRefreshInterceptor } from 'axios-stream-auth-refresh'
import type { AxiosRequestConfig } from 'axios'
const api = axios.create({
baseURL: 'https://api.example.com',
})
const refreshAuthLogic = async (
originalConfig: AxiosRequestConfig
): Promise => {
try {
const refreshToken = localStorage.getItem('refreshToken')
const response = await axios.post('https://api.example.com/auth/refresh', {
refreshToken,
})
const newToken = response?.data?.accessToken
if (!newToken) {
throw new Error('No access token received')
}
localStorage.setItem('accessToken', data.accessToken)
localStorage.setItem('refreshToken', data.refreshToken)
originalConfig.headers = originalConfig.headers || {}
originalConfig.headers.Authorization = Bearer ${newToken}
return true
} catch (error) {
localStorage.clear()
window.location.href = '/login'
throw error
}
}
createStreamRefreshInterceptor(api, refreshAuthLogic)
api.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken')
if (token) {
config.headers.Authorization = Bearer ${token}
}
return config
})
`
$3
`typescript
createStreamRefreshInterceptor(
api,
async (config) => {
// Refresh logic
return true
},
{ statusCodes: [401, 403] }
)
`
$3
`typescript
axios.post('/auth/refresh', data, {
skipAuthRefresh: true,
})
axios.post('/auth/login', credentials, {
skipAuthRefresh: true,
})
`
$3
`typescript
import { createContext, useContext, useEffect } from 'react'
import axios from 'axios'
import { createStreamRefreshInterceptor } from 'axios-stream-auth-refresh'
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL
})
const ApiContext = createContext(api)
export function ApiProvider({ children }) {
useEffect(() => {
createStreamRefreshInterceptor(api, async (config) => {
const refreshToken = localStorage.getItem('refreshToken')
if (!refreshToken) {
window.location.href = '/login'
throw new Error('No refresh token')
}
try {
const { data } = await axios.post('/auth/refresh', {
refreshToken
}, { skipAuthRefresh: true })
localStorage.setItem('accessToken', data.accessToken)
config.headers.Authorization = Bearer ${data.accessToken}
return true
} catch (error) {
localStorage.clear()
window.location.href = '/login'
throw error
}
})
api.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken')
if (token) {
originalConfig.headers = originalConfig.headers || {};
config.headers.Authorization = Bearer ${token}
}
return config
})
}, [])
return {children}
}
export const useApi = () => useContext(ApiContext)
`
$3
`typescript
import axios from 'axios'
import { createStreamRefreshInterceptor } from 'axios-stream-auth-refresh'
export const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
})
createStreamRefreshInterceptor(api, async (config) => {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'include',
})
if (!response.ok) {
throw new Error('Refresh failed')
}
const { accessToken } = await response.json()
config.headers.Authorization = Bearer ${accessToken}
return true
})
`
---
Testing
`bash
npm test
npm run test:ui
npm run test:coverage
`
---
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create your feature branch (git checkout -b feature/amazing-feature)
3. Commit your changes (git commit -m 'Add some amazing feature')
4. Push to the branch (git push origin feature/amazing-feature`)