background
Recently, the user login page of the project is for security consideration. The access_token after login is not permanently valid but effective. Therefore, it is necessary to refresh the access_token without perception when the user login token becomes invalid. If the access_token fails, the front end needs to invoke refresh API once to make the Access_token valid again without affecting user experience.
Demand for dismantling
The API returns {access_Token: “, refresh_token: “, expired: “} after the user has logged in, so that when the user initiates a request, it can determine whether the Access_token is expired and refresh the access_token. The access_token expiration can be determined from expired before the request is made or after the request results come back. The difficulty is how to temporarily store multiple requests when they come in and then restore the request scenario after refresh.
Implementation approach
Demand disassembly mainly needs to solve two problems, one is failure judgment, the other is multiple request restoration, the specific solution is as follows:
- Failure judgment :(1) basis before request
expired
But this operation is not reliable, because the time is modifiable, so this method is not used; (2) Judge according to the request resultaccess_token
Expire, then refresh, then make another request. - Multi-request scenario restoration: Avoid multiple requests when multiple expired requests come in at the same time
refresh
Request to set a flag bit when there is already onerefresh
Pending incoming requests while the operation is in progressPromise
thepending
State to make an argument), waitrefresh
Make the request after success.
The pseudo-code implementation is as follows
import axios, { AxiosRequestConfig } from 'axios';
interface IRequestConfig {
tokenInfoKey: string; // Because the token data after login exists when localstorage requires the key value
onRefreshError: VoidFunction;
}
interface ITokenInfo {
access_token: string;
refresh_token: string;
expired: string;
}
class Request {
private config: IRequestConfig = {};
private tokenInfo: ITokenInfo | null = null;
private isRefreshing: boolean = false;
private needRetryRequest = [];
constructor (config: IRequestConfig) {
const { tokenInfoKey } = config;
this.tokenInfo = JSON.parse(localStorage.getItem(tokenInfoKey));
this.config = config;
}
refreshAccessToken (onSuccess: VoidFunction) {
return axios.request<ITokenInfo>({
url: ' '.method: ' ',
})
.then((res) = > {
localStorage.setItem(this.config.tokenInfoKey, res.data);
onSuccess();
})
.catch(() = > {
this.needRetryRequest = [];
});
}
request (config: AxiosRequestConfig) {
if (this.isRefreshing) {
return new Promise((resolve) = > {
this.needRetryRequest.push(async() = >await resolve(axios));
});
}
return axios
.request(config)
.then(res= > {
return res.data;
})
.catch(e= > {
// Suppose the 401401 definition needs refresh
const code = 401401;
const { status } = e.response;
if (status === code) {
this.isRefreshing = true;
this.refreshAccessToken(() = > {
Promise.all([() = > this.request(config), ... this.needRetryRequest].map(cb= > cb()));
});
}
})
.finally(() = > {
this.isRefreshing = false; }); }}Copy the code