Coder Social home page Coder Social logo

jetbridge / axios-jwt Goto Github PK

View Code? Open in Web Editor NEW
112.0 10.0 30.0 1.82 MB

Store, transmit, refresh JWT authentication tokens for axios

Home Page: https://www.npmjs.com/package/axios-jwt/

TypeScript 99.03% JavaScript 0.97%
axios interceptor jwt typescript

axios-jwt's Introduction

axios-jwt

Store, clear, transmit and automatically refresh JWT authentication tokens. This library can be used in both web and react-native projects.

What does it do?

Applies a request interceptor to your axios instance.

The interceptor automatically adds an access token header (default: Authorization) to all requests. It stores accessToken and refreshToken in localStorage (web) or 'AsyncStorage' (React Native) and reads them when needed.

It parses the expiration time of your access token and checks to see if it is expired before every request. If it has expired, a request to refresh and store a new access token is automatically performed before the request proceeds.

Installation instructions

Install axios-jwt

npm install --save axios-jwt # or `yarn add axios-jwt`

Additional steps for React Native projects

You will also need to install react-native-async-storage in order to be able to store and retrieve tokens.

Expo

expo install @react-native-async-storage/async-storage

React Native

npm install --save @react-native-async-storage/async-storage # or `yarn add @react-native-async-storage/async-storage`
npx pod-install # installs the native iOS packages

How do I use it?

  1. Create an axios instance
  2. Define a token refresh function
  3. Configure the interceptor
  4. Store tokens on login with setAuthTokens()
  5. Clear tokens on logout with clearAuthTokens()

Applying the interceptor

// api.ts

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

const BASE_URL = 'https://api.example.com'

// 1. Create an axios instance that you wish to apply the interceptor to
export const axiosInstance = axios.create({ baseURL: BASE_URL })

// 2. Define token refresh function.
const requestRefresh: TokenRefreshRequest = async (refreshToken: string): Promise<IAuthTokens | string> => {

  // Important! Do NOT use the axios instance that you supplied to applyAuthTokenInterceptor (in our case 'axiosInstance')
  // because this will result in an infinite loop when trying to refresh the token.
  // Use the global axios client or a different instance
  const response = await axios.post(`${BASE_URL}/auth/refresh_token`, { token: refreshToken })

  // If your backend supports rotating refresh tokens, you may also choose to return an object containing both tokens:
  // return {
  //  accessToken: response.data.access_token,
  //  refreshToken: response.data.refresh_token
  //}

  return response.data.access_token
}

// 3. Add interceptor to your axios instance
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 })

Login/logout

// login.ts

import { isLoggedIn, setAuthTokens, clearAuthTokens, getAccessToken, getRefreshToken } from 'axios-jwt'
import { axiosInstance } from './api'

// 4. Post email and password and get tokens in return. Call setAuthTokens with the result.
const login = async (params: ILoginRequest) => {
  const response = await axiosInstance.post('/auth/login', params)

  // save tokens to storage
  setAuthTokens({
    accessToken: response.data.access_token,
    refreshToken: response.data.refresh_token
  })
}

// 5. Remove the auth tokens from storage
const logout = () => clearAuthTokens()

// Check if refresh token exists
if (isLoggedIn()) {
  // assume we are logged in because we have a refresh token
}

// Get access to tokens
const accessToken = getAccessToken()
const refreshToken = getRefreshToken()

Configuration

applyAuthTokenInterceptor(axiosInstance, {
  requestRefresh,  // async function that takes a refreshToken and returns a promise the resolves in a fresh accessToken
  header: "Authorization",  // header name
  headerPrefix: "Bearer ",  // header value prefix
})

Caveats

  • Your backend should allow a few seconds of leeway between when the token expires and when it actually becomes unusable.

Non-TypeScript implementation

import { applyAuthTokenInterceptor, setAuthTokens, clearAuthTokens } from 'axios-jwt';
import axios from 'axios';

const BASE_URL = 'https://api.example.com'

// 1. Create an axios instance that you wish to apply the interceptor to
const axiosInstance = axios.create({ baseURL: BASE_URL })

// 2. Define token refresh function.
const requestRefresh = (refresh) => {
    // Notice that this is the global axios instance, not the axiosInstance!  <-- important
    return axios.post(`${BASE_URL}/auth/refresh_token`, { refresh })
      .then(response => response.data.access_token)
};

// 3. Apply interceptor
applyAuthTokenInterceptor(axiosInstance, { requestRefresh });  // Notice that this uses the axiosInstance instance.  <-- important

// 4. Logging in
const login = async (params) => {
  const response = await axiosInstance.post('/auth/login', params)

  // save tokens to storage
  setAuthTokens({
    accessToken: response.data.access_token,
    refreshToken: response.data.refresh_token
  })
}

// 5. Logging out
const logout = () => clearAuthTokens()

// Now just make all requests using your axiosInstance instance
axiosInstance.get('/api/endpoint/that/requires/login').then(response => { })

axios-jwt's People

Contributors

abereghici avatar adamszeptycki avatar caioaugustoo avatar dependabot[bot] avatar digitalkaoz avatar dizzzmas avatar greenyas avatar moshfrid avatar mvanroon avatar pwfraley avatar rawnly avatar revmischa avatar splurgebudget avatar yohantoledo avatar ziplokk1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

axios-jwt's Issues

[React Native] Not working

Unable to resolve module `stream` from `node_modules/jws/lib/sign-stream.js`: stream could not be found within the project

How to deal with refresh tokens that are expired?

Thanks for creating this cool library!

I was wondering how to deal with expired refresh tokens? Currently, there seems to be no way to handle this. Is it possible to specify a force-logout URL in case the refresh fails with a 401 or 403?

Thanks!

StorageType undefined outside the interceptor?

I'm a back-end coder, so go easy on me here, as this may be outright pilot error.

Setting up and using authTokenInterceptor looks to be working just fine, thanks for that - but, when trying to follow the examples in the README, to call e.g. setAuthTokens, isLoggedIn, etc outside the context of the interceptor, it seems like StorageProxy.Storage is just returning null, so those calls simply do nothing.

What am I missing here? If one calls setAuthTokens such as in the Login/Logout example, where is the storage actually being set?

I'm happy to give more detail or code snips, etc, as needed.

localstorage is not defined

It seem that the storage abstraction, specifically exporting localstorage in the storage.ts file, is breaking server-side code.

image

'getStorage' does not exist in type 'IAuthTokenInterceptorConfig'

Seems like there's a problem with types:

My test code

import axios from 'axios'
import { applyAuthTokenInterceptor } from 'axios-jwt'

const api = axios.create()

applyAuthTokenInterceptor(api, { getStorage: () => window.sessionStorage })

Exception

src/index.ts(6,34): error TS2345: Argument of type '{ getStorage: () => Storage; }' is not assignable to parameter of type 'IAuthTokenInterceptorConfig'.
  Object literal may only specify known properties, and 'getStorage' does not exist in type 'IAuthTokenInterceptorConfig'.

Error: error occured in dts build
    at Worker.<anonymous> (/Users/federicovitale/Developer/projects/new-proj/node_modules/.pnpm/[email protected][email protected]/node_modules/tsup/dist/index.js:2181:26)
    at Worker.emit (node:events:527:28)
    at MessagePort.<anonymous> (node:internal/worker:232:53)
    at MessagePort.[nodejs.internal.kHybridDispatch] (node:internal/event_target:643:20)
    at MessagePort.exports.emitMessage (node:internal/per_context/messageport:23:28)
Error: Failed to compile. Check the logs above.
    at error (/Users/federicovitale/Developer/projects/new-proj/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:198:30)
    at throwPluginError (/Users/federicovitale/Developer/projects/new-proj/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:21718:12)
    at Object.error (/Users/federicovitale/Developer/projects/new-proj/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:22672:20)
    at Object.error (/Users/federicovitale/Developer/projects/new-proj/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:21895:42)
    at Object.transform (/Users/federicovitale/Developer/projects/new-proj/node_modules/.pnpm/[email protected][email protected]/node_modules/tsup/dist/rollup.js:7217:20)
    at /Users/federicovitale/Developer/projects/new-proj/node_modules/.pnpm/[email protected]/node_modules/rollup/dist/shared/rollup.js:22879:40
[TS-PACKAGE] DTS Build error
 ELIFECYCLE  Command failed with exit code 1.

Retry failed request after RefreshToken

it would be nice to have a general retry handler aswell:

const onOk = (response) => response;
const onError = (error) => {
	// Return any error which is not due to authentication back to the calling service
	if (error && error.response && error.response.status !== 401) {
		return Promise.reject(error);
	}

	const config = error.config;

	// Logout user if token refresh didn't work or user is disabled
	if (error.config.url.endsWith("auth/refresh")) {
		logout();
		return Promise.reject(error);
	}

	// Try request again with new token
	return new Promise((resolve, reject) => {
		requestRefresh().then((response) => {
			setAuthTokens(response.data)
			return http.request(config).then((response) => resolve(response));
		}).catch((e) => {
			clearAuthTokens()
			reject(e)
		})
	});
};

http.interceptors.response.use(onOk, onError);

Request without the interceptor

I would like to know if there is a way to not use the interceptor in some calls, sometimes I have call which do not require the tokens.

No requests are made if applyAuthTokenInterceptor called

Hello, im using React 18 and axios 1.4.0
This is how I define the axios instance (following the tutorial)

const BASE_URL = 'http://localhost:8080/api'

// 1. Create an axios instance that you wish to apply the interceptor to
export const apiAuthInstance = axios.create({ baseURL: BASE_URL })

// 2. Define token refresh function.
const requestRefresh: TokenRefreshRequest = async (refreshToken: string): Promise<IAuthTokens | string> => {
 
  const response = await axios.post(`${BASE_URL}/auth/refresh_token`, { token: refreshToken })
  return response.data.access_token
}

// 3. Add interceptor to your axios instance
applyAuthTokenInterceptor(apiAuthInstance, { requestRefresh })

// If applyAuthTokenInterceptor is applied, then I cant make any api calls at all
// Here "/asd" wont be called, or any other place it just doesnt make the call. 
// What am I doing wrong?
apiAuthInstance.post("/asd")

Allow a different storage.

Hello, I would like to store my credentials inside a cookie. I need them available for SSR reasons. Having the ability to specify a custom storage would be nice.

It could be done extending the existing Storage interface and adding it to the config object.

Uncaught TypeError: (0 , ms_1.default) is not a function

Appears I am missing the "ms" dependency, installing it doesn't resolve the issue however

Uncaught TypeError: (0 , ms_1.default) is not a function
at authTokenInterceptor (authTokenInterceptor. ts:178:1)
at app lyAuthTokenInterceptor (applyAuthTokenInterceptor.ts:16:1)
at ./src/api/auth.ts (auth.ts:43:1

Feature Request: Allow prepended string for token names

I have an application with two sets of tokens for two sets of users in the same application and would like to prepend a string to the stored token so they are distinct. I need the lifecycle on each set of access and refresh token to be distinct.

Publish correct build to NPM

npmjs.com shows version 1.0.4 of this plugin: https://www.npmjs.com/package/axios-jwt

It seems to be quite different from master in this repo. In that version, TokenRefreshRequest returns Promise<string> and does not seem to ever update the refresh token.

Looking at master here, the refresh function returns Promise<IAuthTokens>. Interestingly enough, package.json declares this to be version 1.0.1.

Return interceptor

I suggest adding a return to the end of useAuthTokenInterceptor so that the interceptor is returned, so that it may later be ejected if needed.

return axios.interceptors.request.use(authTokenInterceptor(config))

Question

I have a question, by default the localStorage Storage uses auth-tokens-${process.env.NODE_ENV} as a name for the local storage.

My Question:
What is the correct way to override this?

Thanks

Non-typescript usage examples?

I'm trying to set up JWT tokens for my axios-powered API handler. I want it to automatically refresh tokens if there's an issue without forcing a retry on the request, as well. Seems well and good.

I found this library and am working to implement it, but the examples seem to be TypeScript-based, but I'm not using TypeScript. Would be helpful to have examples for regular JS.

Breaks on webpack 5

Apparently it uses node.js modules under the hood which are no longer bundled by webpack 5.


After going through the source code I believe the refresh strategy is not the best. Instead of checking the expiration time of the token on each request (parsing jwt is time consuming) one should check the response status for 401 status code or call a user provided callback to check if the token should be refreshed. Initiate a refresh if any condition is met. At the same time queue all the outgoing requests and also queue any request that was fired prior to the refresh and returned with 401 status.

not setting header

Added all code per the read me into a REACT (non-type script project)

Here is my code:

import { applyAuthTokenInterceptor } from 'axios-jwt';
import axios from 'axios';

const BASE_URL = 'http://localhost:5000/api/';

const axiosInstance = axios.create({ baseURL: BASE_URL });

const requestRefresh = (refresh) => {
  // Notice that this is the global axios instance, not the axiosInstance!  <-- important
  return axios.post(`${BASE_URL}/auth/refreshtoken`, { refresh }).then((response) => response.data.token);
};

applyAuthTokenInterceptor(axiosInstance, { requestRefresh }); // Notice that this uses the axiosInstance instance.  <-- important

export default axiosInstance;

Dependencies:

  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.6.2",
    "axios-jwt": "^3.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.20.1",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },

[React Native] Not working

Unable to resolve module `stream` from `node_modules/jws/lib/sign-stream.js`: stream could not be found within the project

undefined window and Synchronous isLoggedIn

For some reason, the new swappable storage interface has caused isLoggedIn to not excecute properly on initial page load. For example, in my app im using it to set the ui state for logged in vs not logged in users and now it returns false on page load (onmount useeffect) and true only later, rendering the wrong ui state.

Furthermore, when trying to build the app, getBrowserLocalStorage is causing a ReferenceError: window is not defined t be thrown

Support rotating refresh tokens

Some backends also rotate refresh tokens when exchanging your access token for a fresh one. This means that the backend will present a new refresh token in addition to the new access token.

I'll create a PR somewhere this weekend to support this.

Bug in README.md JavaScript Version

In the README.md you give examples for the JavaScript Version,

in there you call the setAuthTokens and clearAuthTokens, this throws an error unknown funtion, because you are not importing those functions.

So the Import in the JavaScript Version should be changed from:

import { applyAuthTokenInterceptor } from 'axios-jwt';

To:

import { applyAuthTokenInterceptor, setAuthTokens, clearAuthTokens } from 'axios-jwt';

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.