Insensitive refresh token

When it comes to the login token, the product asks a question: can I make the token expire a little longer? I have to log in frequently.

Front end: back end, can you set the token expiration time longer.

Back end: Yes, but that's not safe. You can use a better method.

Front end: what method?

Back end: give you the interface to refresh the token, and refresh the token regularly

Front end: OK, let me think about it

**

demand

**
When the token expires, refresh the token. The front end needs to refresh the token without feeling, that is, when brushing the token, the user should be unaware and avoid frequent login. Realization idea

Method 1
The back end returns the expiration time. The front end judges the expiration time of the token and calls the refresh token interface

Disadvantages: the backend needs to provide an additional token expiration time field; The local time judgment is used. If the local time is tampered with, especially when the local time is slower than the server time, the interception will fail.

Method 2
Write a timer and refresh the token interface regularly

Disadvantages: it wastes resources and consumes performance. It is not recommended.

Method 3
In response to interceptor intercepting, it is called to refresh the token interface after judging that token returns to expire.

realization

The basic skeleton of axios, using service interceptors. Response to intercept

import axios from 'axios'

service.interceptors.response.use(
  response => {
    if (response.data.code === 409) {
        return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => {
          const { token } = res.data
          setToken(token)
          response.headers.Authorization = `${token}`
        }).catch(err => {
          removeToken()
          router.push('/login')
          return Promise.reject(err)
        })
    }
    return response && response.data
  },
  (error) => {
    Message.error(error.response.data.msg)
    return Promise.reject(error)
  }
)

Problem solving

Question 1: how to prevent multiple refresh of token s
We use a variable isRefreshing to control whether the state of the token is being refreshed.

import axios from 'axios'

service.interceptors.response.use(
  response => {
    if (response.data.code === 409) {
      if (!isRefreshing) {
        isRefreshing = true
        return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => {
          const { token } = res.data
          setToken(token)
          response.headers.Authorization = `${token}`
        }).catch(err => {
          removeToken()
          router.push('/login')
          return Promise.reject(err)
        }).finally(() => {
          isRefreshing = false
        })
      }
    }
    return response && response.data
  },
  (error) => {
    Message.error(error.response.data.msg)
    return Promise.reject(error)
  }
)

Question 2: how can other interfaces solve two or more requests at the same time
When the second expired request comes in and the token is being refreshed, we first save the request to an array queue and try to keep the request waiting until the token is refreshed, and then try to empty the request queue one by one. So how do you keep this request waiting? In order to solve this problem, we have to use Promise. After the request is stored in the queue, a Promise is returned at the same time. Keep the Promise in the Pending state (that is, do not call resolve). At this time, the request will wait and wait. As long as we do not execute resolve, the request will wait all the time. When the interface of the refresh request returns, we call resolve and try again one by one. Final code:

import axios from 'axios'

// Are you refreshing your tags
let isRefreshing = false
//Retry Queue 
let requests = []
service.interceptors.response.use(
  response => {
  //Contract code 409 token expired
    if (response.data.code === 409) {
      if (!isRefreshing) {
        isRefreshing = true
        //Call the interface to refresh the token
        return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => {
          const { token } = res.data
          // Replace token
          setToken(token)
          response.headers.Authorization = `${token}`
        }).catch(err => {
        //Skip to login page
          removeToken()
          router.push('/login')
          return Promise.reject(err)
        }).finally(() => {
          isRefreshing = false
        })
      } else {
        // Return Promise without resolve
        return new Promise(resolve => {
          // Store resolve as a function and wait for the refresh before executing
          requests.push(token => {
            response.headers.Authorization = `${token}`
            resolve(service(response.config))
          })
        })
      }
    }
    return response && response.data
  },
  (error) => {
    Message.error(error.response.data.msg)
    return Promise.reject(error)
  }
)

The product requirements are completed. I don't know if there is a better solution.

Keywords: Javascript Front-end Vue Vue.js

Added by billspeg on Fri, 21 Jan 2022 14:08:58 +0200