[英]Problem with JWT Refresh Token Flow with axios/axios-auth-refresh
(我在这里阅读了许多类似的问题,并且大多数/所有人都说要使用不同的 axios 实例来处理刷新令牌请求(与 API 请求相比)。但是,我不清楚这将如何工作,因为我正在使用axios-auth-refresh来自动刷新访问令牌。)
我正在为后端 API 请求使用基于 JWT 的身份验证流程的应用程序。 一般流程运行良好; 登录后,用户会获得一个长期刷新令牌和短期访问令牌。 使用 axios 的axios-auth-refresh插件,我可以在访问令牌过期时自动刷新它。
我的问题是,当刷新令牌过期时,我无法捕获错误并将用户重定向以重新进行身份验证。 我尝试过的任何方法都无法捕捉到错误。 自动刷新挂钩的(当前)代码是:
const refreshAuth = (failed) =>
axios({ method: "post", url: "token", skipAuthRefresh: true })
.then(({ status, data: { success, accessToken } }) => {
console.warn(`status=${status}`);
if (!success) Promise.reject(failed);
processToken(accessToken);
// eslint-disable-next-line no-param-reassign
failed.response.config.headers.Authorization = `Bearer ${accessToken}`;
return Promise.resolve();
})
.catch((error) => console.error("%o", error));
createAuthRefreshInterceptor(axios, refreshAuth);
在刷新令牌过时或丢失的情况下,我既看不到status=xxx
控制台行,也看不到catch()
块中错误 object 的转储。
此处的实际文件位于 GitHub 上,尽管它与上面的工作版本略有不同。 主要是,在 GH 版本中,钩子调用axios.post("token").then(...)
在上面我进行更明确的调用以添加skipAuthRefresh
参数。 添加它让我在控制台中获得了更详细的错误跟踪,但我仍然没有通过catch()
捕捉到 401 响应。
我已经尝试了所有我能想到的东西......有什么东西会跳出来作为我缺少的东西吗?
兰迪
(编辑以确保 GitHub 链接指向有问题的文件版本。)
自发布此消息以来,我已经设法解决了这个问题并提出了一个可行的解决方案。
解决方案的关键实际上在于使用不同的 axios 实例来调用更新刷新令牌。 我创建了第二个模块来封装第二个 axios 实例,该实例不会获取由axios-auth-refresh
模块创建的拦截器。 在解决了最初导致的一些无意的循环依赖问题后,我发现当刷新令牌本身过时或丢失时,我可以看到 axios 抛出异常。
(有趣的是,这导致了另一个问题:一旦我发现刷新令牌不再有效,我需要注销用户并让他们返回登录屏幕。因为这里的应用程序是一个 React 应用程序,所以身份验证正在使用只能在组件内调用的自定义钩子进行处理。但是,我已将所有 API 调用抽象到一个非 React 模块中,以便我可以封装诸如添加Authorization
header、基础 ZE6B7031A8D2C45845 等. 在那个级别,我无法运行 auth 挂钩来访问注销逻辑。我通过在查询 object(一个反应查询对象)上放置一个默认的onError
处理程序来解决这个问题,我用于所有 API 调用。)
我基于此 SO 答案中的Request
class 来刷新令牌并处理刷新失败。
现在我的Request
看起来像这样:
import axios from "axios";
import {getLocalStorageToken, logOut, refreshToken} from "./authentication";
class Request {
ADD_AUTH_CONFIG_HEADER = 'addAuth'
constructor() {
this.baseURL = process.env.REACT_APP_USER_ROUTE;
this.isRefreshing = false;
this.failedRequests = [];
this.axios = axios.create({
baseURL: process.env.REACT_APP_USER_ROUTE,
headers: {
clientSecret: this.clientSecret,
},
});
this.beforeRequest = this.beforeRequest.bind(this);
this.onRequestFailure = this.onRequestFailure.bind(this);
this.processQueue = this.processQueue.bind(this);
this.axios.interceptors.request.use(this.beforeRequest);//<- Intercepting request to add token
this.axios.interceptors.response.use(this.onRequestSuccess,
this.onRequestFailure);// <- Intercepting 401 failures
}
beforeRequest(request) {
if (request.headers[this.ADD_AUTH_CONFIG_HEADER] === true) {
delete request.headers[this.ADD_AUTH_CONFIG_HEADER];
const token = getLocalStorageToken();//<- replace getLocalStorageToken with your own way to retrieve your current token
request.headers.Authorization = `Bearer ${token}`;
}
return request;
}
onRequestSuccess(response) {
return response.data;
}
async onRequestFailure(err) {
console.error('Request failed', err)
const {response} = err;
const originalRequest = err.config;
if (response.status === 401 && err && originalRequest && !originalRequest.__isRetryRequest) {
if (this.isRefreshing) {
try {
const token = await new Promise((resolve, reject) => {//<- Queuing new request while token is refreshing and waiting until they get resolved
this.failedRequests.push({resolve, reject});
});
originalRequest.headers.Authorization = `Bearer ${token}`;
return this.axios(originalRequest);
} catch (e) {
return e;
}
}
this.isRefreshing = true;
originalRequest.__isRetryRequest = true;
console.log('Retrying request')
console.log('Previous token', getLocalStorageToken())
try {
const newToken = await refreshToken()//<- replace refreshToken with your own method to get a new token (async)
console.log('New token', newToken)
originalRequest.headers.Authorization = `Bearer ${newToken}`;
this.isRefreshing = false;
this.processQueue(null, newToken);
return this.axios(originalRequest)
} catch (err) {
console.error('Error refreshing the token, logging out', err);
await logOut();//<- your logout function (clean token)
this.processQueue(err, null);
throw response;//<- return the response to check on component layer whether response.status === 401 and push history to log in screen
}
}
throw response;
}
processQueue(error, token = null) {
this.failedRequests.forEach((prom) => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
this.failedRequests = [];
}
}
const request = new Request();
export default request;
我的问题是,当刷新令牌过期时,我无法捕获错误并将用户重定向以重新进行身份验证。 我尝试过的任何方法都无法捕捉到错误。 自动刷新挂钩的(当前)代码是:
如果访问令牌过期,您的 api 的返回码是什么?
如果它不同于 401(默认),则需要配置,请参阅 exanoke 403:
createAuthRefreshInterceptor(axios, refreshAuthLogic, {
statusCodes: [ 401, 403 ] // default: [ 401 ]
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.