2019年9月1日 星期日

[Axios] Interceptor - Refresh access token


 Axios   Interceptor   Refresh Token   Typescript 



Introduction


Axios is the Promise based HTTP client, and is often used in my Vue.js projects.
This article shows how to use Axios Interceptor to refresh the JWT token when the origin token had expired.

The flow (activity diagram) is as following.





The concept and sample codes in based on Axios interceptor by Typescript.
However, they can be apply to other Http Client framework, and of course, also write the code in Javascript.




Environment


axios 0.19.0
vuex 3.0.1
typescript 3.2.1




Implement

          
Vuex: store Access token and Refresh token

I use Vuex store to keep the Access-token (JWT token) and Refresh-token.
And create an Vuex Action to send request to refreshing-token API and then update the new Access-token.

If you are not familiar with Vuex, here are my tutorials about it!


Model: UserToken.ts

export default class UserToken {

  public accessToken: string | null;
  public expiresIn: number | null;
  public refreshToken: string | null;
  constructor(fields?: {
    accessToken?: string,
    expiresIn?: string,
    refreshToken?: string,
  }) {
    if (fields) {
        Object.assign(this, fields);
    }
  }
}




token-store.ts


import { UserToken } from '../classes/apiModel';
import axios from '../modules/axios-module';

export default {
  state: {
    tokens: {} as UserToken,
  },
  getters: {
    tokens: (state: any) => state.tokens,
  },
  mutations: {
    setTokens(state: any, tokens: UserToken) {
      state.tokens = tokens;
    }
  },
  actions: {

    async refreshToken({ commit, state }: { commit: any, state: any}) {
      const isRefreshOk = true;
      if (!state.tokens || !state.tokens.refreshToken) {
        return !isRefreshOk;
      }
      try {
        const url = 'api/Auth/RefreshToken';
        const res: any = await axios.post(state.tokens.refreshToken);
        const userToken = res.data;
        // Mutation
        commit('setTokens', userToken);
        // Return true/false for OK/NG
        return !!userToken ;
      } catch (err) {
        throw err;
      }
    },
  },
};





Axios: Interceptor

We would like Axios to send request with Access token on Http header automatically.
This can be done by setting request’s configuration of interceptor like this,

axios-module.ts

const instance = axios.create();

instance.interceptors.request.use( (config: AxiosRequestConfig) => {

  // Optional headers
  if (store.state.tokenStore && store.state.tokenStore.tokens) {
    const token = store.state.tokenStore.tokens.accessToken;
    config.headers.Authorization = `Bearer ${token}`;
  }

  return config;

}, (error) => {
  return Promise.reject(error);
});




Now we can implement that when receiving 498 response, dispatching “refreshToken” to get the NEW Access-Token, and re-send the failed request.

axios-module.ts

const instance = axios.create();

let isAlreadyFetchingAccessToken = false;

instance.interceptors.response.use( (response: AxiosResponse<any>) => {
  return response;
}, async (error: any) => {
 
  const { config, response: { status } } = error;
  const originalRequest = config;
  let isRefreshOk: boolean = false;

  if (status === 498) {
    if (!isAlreadyFetchingAccessToken) {
      isAlreadyFetchingAccessToken = true;
      isRefreshOk = await store.dispatch('refreshToken');
      isAlreadyFetchingAccessToken = false;
    }

    if (isRefreshOk) {
      const retryOriginalRequest = new Promise((resolve) => {
          resolve(instance(originalRequest));
      });
      return retryOriginalRequest; // Return the resposne from original request with new token
    } else {
      return Promise.reject(error); // Return original response
    }
  } else { // Non 498 response
   return Promise.reject(error);
  }
});

export default instance;



Demo

1.  Send a request to API: [GetAll] and received 498 response
2.  Send a request to API: [RefreshToken]
3.  Re-send a request to API: [GetAll] and OK




Look into the response of refreshing token, we got a new token: “…KAWai_yRG7UXLfYwwFg”




And the new token was exactly what we used in the last request.



Source Code





Reference







沒有留言:

張貼留言