Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

axios-jwt

Package Overview
Dependencies
Maintainers
3
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

axios-jwt - npm Package Compare versions

Comparing version 2.1.1 to 2.3.0

dist/jest.setup.d.ts

1

jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['./jest.setup.ts']
}

2

package.json
{
"name": "axios-jwt",
"version": "2.1.1",
"version": "2.3.0",
"description": "Axios interceptor to store, use, and refresh tokens for authentication.",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

@@ -53,3 +53,3 @@ # axios-jwt

import { IAuthTokens, TokenRefreshRequest, applyAuthTokenInterceptor } from 'axios-jwt'
import { IAuthTokens, TokenRefreshRequest, applyAuthTokenInterceptor, getBrowserLocalStorage } from 'axios-jwt'
import axios from 'axios'

@@ -81,2 +81,8 @@

applyAuthTokenInterceptor(axiosInstance, { requestRefresh })
// New to 2.2.0+: initialize with storage: localStorage/sessionStorage/nativeStorage. Helpers: getBrowserLocalStorage, getBrowserSessionStorage
const getStorage = getBrowserLocalStorage
// You can create you own storage, it has to comply with type StorageType
applyAuthTokenInterceptor(axiosInstance, { requestRefresh, getStorage })
```

@@ -83,0 +89,0 @@

@@ -1,113 +0,6 @@

import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import jwtDecode from 'jwt-decode'
import type { JwtPayload } from 'jwt-decode'
import Storage from './storage'
import { applyAuthTokenInterceptor } from './applyAuthTokenInterceptor'
// a little time before expiration to try refresh (seconds)
const EXPIRE_FUDGE = 10
export const STORAGE_KEY = `auth-tokens-${process.env.NODE_ENV}`
type Token = string
export interface IAuthTokens {
accessToken: Token
refreshToken: Token
}
// EXPORTS
/**
* Checks if refresh tokens are stored
* @returns Whether the user is logged in or not
*/
export const isLoggedIn = (): boolean => {
const token = getRefreshToken()
return !!token
}
/**
* Sets the access and refresh tokens
* @param {IAuthTokens} tokens - Access and Refresh tokens
*/
export const setAuthTokens = (tokens: IAuthTokens): void =>
Storage.setItem(STORAGE_KEY, JSON.stringify(tokens))
/**
* Sets the access token
* @param {string} token - Access token
*/
export const setAccessToken = (token: Token): void => {
const tokens = getAuthTokens()
if (!tokens) {
throw new Error('Unable to update access token since there are not tokens currently stored')
}
tokens.accessToken = token
setAuthTokens(tokens)
}
/**
* Clears both tokens
*/
export const clearAuthTokens = (): void => Storage.removeItem(STORAGE_KEY)
/**
* Returns the stored refresh token
* @returns {string} Refresh token
*/
export const getRefreshToken = (): Token | undefined => {
const tokens = getAuthTokens()
return tokens ? tokens.refreshToken : undefined
}
/**
* Returns the stored access token
* @returns {string} Access token
*/
export const getAccessToken = (): Token | undefined => {
const tokens = getAuthTokens()
return tokens ? tokens.accessToken : undefined
}
/**
* @callback requestRefresh
* @param {string} refreshToken - Token that is sent to the backend
* @returns {Promise} Promise that resolves in an access token
*/
/**
* Gets the current access token, exchanges it with a new one if it's expired and then returns the token.
* @param {requestRefresh} requestRefresh - Function that is used to get a new access token
* @returns {string} Access token
*/
export const refreshTokenIfNeeded = async (
requestRefresh: TokenRefreshRequest
): Promise<Token | undefined> => {
// use access token (if we have it)
let accessToken = getAccessToken()
// check if access token is expired
if (!accessToken || isTokenExpired(accessToken)) {
// do refresh
accessToken = await refreshToken(requestRefresh)
}
return accessToken
}
/**
*
* @param {Axios} axios - Axios instance to apply the interceptor to
* @param {IAuthTokenInterceptorConfig} config - Configuration for the interceptor
*/
export const applyAuthTokenInterceptor = (
axios: AxiosInstance,
config: IAuthTokenInterceptorConfig
): void => {
if (!axios.interceptors) throw new Error(`invalid axios instance: ${axios}`)
axios.interceptors.request.use(authTokenInterceptor(config))
}
/**
* @deprecated This method has been renamed to applyAuthTokenInterceptor and will be removed in a future release.

@@ -117,200 +10,11 @@ */

// PRIVATE
/**
* Returns the refresh and access tokens
* @returns {IAuthTokens} Object containing refresh and access tokens
*/
const getAuthTokens = (): IAuthTokens | undefined => {
const rawTokens = Storage.getItem(STORAGE_KEY)
if (!rawTokens) return
try {
// parse stored tokens JSON
return JSON.parse(rawTokens)
} catch (error: unknown) {
if (error instanceof SyntaxError) {
error.message = `Failed to parse auth tokens: ${rawTokens}`
throw error
}
}
}
/**
* Checks if the token is undefined, has expired or is about the expire
*
* @param {string} token - Access token
* @returns Whether or not the token is undefined, has expired or is about the expire
*/
const isTokenExpired = (token: Token): boolean => {
if (!token) return true
const expiresIn = getExpiresIn(token)
return !expiresIn || expiresIn <= EXPIRE_FUDGE
}
/**
* Gets the unix timestamp from an access token
*
* @param {string} token - Access token
* @returns {string} Unix timestamp
*/
const getTimestampFromToken = (token: Token): number | undefined => {
const decoded = jwtDecode<JwtPayload>(token)
return decoded.exp
}
/**
* Returns the number of seconds before the access token expires or -1 if it already has
*
* @param {string} token - Access token
* @returns {number} Number of seconds before the access token expires
*/
const getExpiresIn = (token: Token): number => {
const expiration = getTimestampFromToken(token)
if (!expiration) return -1
return expiration - Date.now() / 1000
}
/**
* Refreshes the access token using the provided function
*
* @param {requestRefresh} requestRefresh - Function that is used to get a new access token
* @returns {string} - Fresh access token
*/
const refreshToken = async (requestRefresh: TokenRefreshRequest): Promise<Token> => {
const refreshToken = getRefreshToken()
if (!refreshToken) throw new Error('No refresh token available')
try {
isRefreshing = true
// Refresh and store access token using the supplied refresh function
const newTokens = await requestRefresh(refreshToken)
if (typeof newTokens === 'object' && newTokens?.accessToken) {
await setAuthTokens(newTokens)
return newTokens.accessToken
} else if (typeof newTokens === 'string') {
await setAccessToken(newTokens)
return newTokens
}
throw new Error('requestRefresh must either return a string or an object with an accessToken')
} catch (error) {
// Failed to refresh token
if (axios.isAxiosError(error)) {
const status = error.response?.status
if (status === 401 || status === 422) {
// The refresh token is invalid so remove the stored tokens
Storage.removeItem(STORAGE_KEY)
throw new Error(`Got ${status} on token refresh; clearing both auth tokens`)
}
}
// A different error, probably network error
if (error instanceof Error) {
throw new Error(`Failed to refresh auth token: ${error.message}`)
} else {
throw new Error('Failed to refresh auth token and failed to parse error')
}
} finally {
isRefreshing = false
}
}
export type TokenRefreshRequest = (refreshToken: Token) => Promise<Token | IAuthTokens>
export interface IAuthTokenInterceptorConfig {
header?: string
headerPrefix?: string
requestRefresh: TokenRefreshRequest
}
/**
* Function that returns an Axios Intercepter that:
* - Applies that right auth header to requests
* - Refreshes the access token when needed
* - Puts subsequent requests in a queue and executes them in order after the access token has been refreshed.
*
* @param {IAuthTokenInterceptorConfig} config - Configuration for the interceptor
* @returns {Promise} Promise that resolves in the supplied requestConfig
*/
export const authTokenInterceptor =
({
header = 'Authorization',
headerPrefix = 'Bearer ',
requestRefresh,
}: IAuthTokenInterceptorConfig) =>
async (requestConfig: AxiosRequestConfig): Promise<AxiosRequestConfig> => {
// We need refresh token to do any authenticated requests
if (!getRefreshToken()) return requestConfig
// Queue the request if another refresh request is currently happening
if (isRefreshing) {
return new Promise((resolve, reject) => {
queue.push({ resolve, reject })
})
.then((token) => {
if (requestConfig.headers) {
requestConfig.headers[header] = `${headerPrefix}${token}`
}
return requestConfig
})
.catch(Promise.reject)
}
// Do refresh if needed
let accessToken
try {
accessToken = await refreshTokenIfNeeded(requestRefresh)
resolveQueue(accessToken)
} catch (error: unknown) {
if (error instanceof Error) {
declineQueue(error)
throw new Error(
`Unable to refresh access token for request due to token refresh error: ${error.message}`
)
}
}
// add token to headers
if (accessToken && requestConfig.headers) {
requestConfig.headers[header] = `${headerPrefix}${accessToken}`
}
return requestConfig
}
type RequestsQueue = {
resolve: (value?: unknown) => void
reject: (reason?: unknown) => void
}[]
let isRefreshing = false
let queue: RequestsQueue = []
/**
* Function that resolves all items in the queue with the provided token
* @param token New access token
*/
const resolveQueue = (token?: Token) => {
queue.forEach((p) => {
p.resolve(token)
})
queue = []
}
/**
* Function that declines all items in the queue with the provided error
* @param error Error
*/
const declineQueue = (error: Error) => {
queue.forEach((p) => {
p.reject(error)
})
queue = []
}
export * from './tokensUtils'
export * from './authTokenInterceptor'
export * from './setAuthTokens'
export * from './applyAuthTokenInterceptor'
export * from './getBrowserSessionStorage'
export * from './getBrowserLocalStorage'
export * from './IAuthTokens'
export * from './TokenRefreshRequest'
export * from './setAuthTokens'
export * from './StorageType'

@@ -1,2 +0,2 @@

import axios from 'axios'
import axios from 'axios';
import { applyAuthTokenInterceptor } from '../src'

@@ -3,0 +3,0 @@

@@ -1,6 +0,5 @@

import { AxiosRequestConfig } from 'axios'
import jwt from 'jsonwebtoken'
import { AxiosRequestConfig } from 'axios';
import jwt from 'jsonwebtoken';
import { authTokenInterceptor } from '../src';
import { authTokenInterceptor } from '../src'
describe('authTokenInterceptor', () => {

@@ -7,0 +6,0 @@ it('returns the original request config if refresh token is not set', async () => {

@@ -1,3 +0,5 @@

import { STORAGE_KEY, clearAuthTokens } from '../src'
import { STORAGE_KEY } from '../src/StorageKey';
import { clearAuthTokens } from '../src';
describe('clearAuthTokens', () => {

@@ -4,0 +6,0 @@ it('removes the tokens from localstorage', () => {

@@ -1,32 +0,78 @@

import { STORAGE_KEY, getAccessToken } from '../src'
import { getAccessToken, authTokenInterceptor, getBrowserSessionStorage } from '../src';
import { STORAGE_KEY } from '../src/StorageKey';
describe('getAccessToken', () => {
it('returns undefined if tokens are not set', () => {
// GIVEN
// localStorage is empty
localStorage.removeItem(STORAGE_KEY)
beforeEach(function () {
window.localStorage.clear()
window.sessionStorage.clear()
})
// WHEN
// I call getAccessToken
const result = getAccessToken()
describe('for localStorage', function () {
it('returns undefined if tokens are not set', () => {
// GIVEN
// localStorage is empty
localStorage.removeItem(STORAGE_KEY)
// THEN
// I expect the result to be undefined
expect(result).toEqual(undefined)
})
// WHEN
// I call getAccessToken
const result = getAccessToken()
it('returns the access token is it is set', () => {
// GIVEN
// Both tokens are stored in localstorage
const tokens = { accessToken: 'accesstoken', refreshToken: 'refreshtoken' }
localStorage.setItem(STORAGE_KEY, JSON.stringify(tokens))
// THEN
// I expect the result to be undefined
expect(result).toEqual(undefined)
})
// WHEN
// I call getAccessToken
const result = getAccessToken()
it('returns the access token is it is set', () => {
// GIVEN
// Both tokens are stored in localstorage
const tokens = { accessToken: 'accesstoken', refreshToken: 'refreshtoken' }
localStorage.setItem(STORAGE_KEY, JSON.stringify(tokens))
// THEN
// I expect the result to be the supplied access token
expect(result).toEqual('accesstoken')
// WHEN
// I call getAccessToken
const result = getAccessToken()
// THEN
// I expect the result to be the supplied access token
expect(result).toEqual('accesstoken')
})
});
describe('for sessionStorage', function () {
beforeEach( () => {
const getStorage = getBrowserSessionStorage
const requestRefresh = jest.fn()
authTokenInterceptor({getStorage, requestRefresh })
})
it('returns undefined if tokens are not set', () => {
// GIVEN
// localStorage is empty
sessionStorage.removeItem(STORAGE_KEY)
// WHEN
// I call getAccessToken
const result = getAccessToken()
// THEN
// I expect the result to be undefined
expect(result).toEqual(undefined)
})
it('returns the access token is it is set', () => {
// GIVEN
// Both tokens are stored in localstorage
const tokens = { accessToken: 'accesstoken_session', refreshToken: 'refreshtoken_session' }
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(tokens))
// WHEN
// I call getAccessToken
const result = getAccessToken()
// THEN
// I expect the result to be the supplied access token
expect(result).toEqual('accesstoken_session')
})
})
})

@@ -1,2 +0,3 @@

import { STORAGE_KEY, getRefreshToken } from '../src'
import { getRefreshToken } from '../src';
import { STORAGE_KEY } from '../src/StorageKey';

@@ -3,0 +4,0 @@ describe('getRefreshToken', () => {

@@ -1,2 +0,3 @@

import { STORAGE_KEY, isLoggedIn } from '../src'
import { STORAGE_KEY } from '../src/StorageKey';
import { isLoggedIn } from '../src';

@@ -3,0 +4,0 @@ describe('isLoggedIn', () => {

@@ -1,4 +0,5 @@

import { STORAGE_KEY, refreshTokenIfNeeded } from '../src'
import jwt from 'jsonwebtoken'
import { refreshTokenIfNeeded } from '../src';
import jwt from 'jsonwebtoken';
import { AxiosError } from 'axios'
import { STORAGE_KEY } from '../src/StorageKey';

@@ -5,0 +6,0 @@ function makeAxiosErrorWithStatusCode(statusCode: number) {

@@ -1,2 +0,3 @@

import { STORAGE_KEY, setAccessToken } from '../src'
import { setAccessToken } from '../src';
import { STORAGE_KEY } from '../src/StorageKey';

@@ -3,0 +4,0 @@ describe('setAccessToken', () => {

@@ -1,2 +0,3 @@

import { STORAGE_KEY, setAuthTokens } from '../src'
import { setAuthTokens } from '../src';
import { STORAGE_KEY } from '../src/StorageKey';

@@ -3,0 +4,0 @@ describe('setAuthTokens', () => {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc