【问题标题】:Problem with JWT Refresh Token Flow with axios/axios-auth-refresh使用 axios/axios-auth-refresh 的 JWT 刷新令牌流问题
【发布时间】:2021-06-12 02:36:17
【问题描述】:

(我在这里阅读了许多类似的问题,并且大多数/所有人都说要对刷新令牌请求使用不同的 axios 实例(与 API 请求相比)。但是,我不清楚这将如何工作,因为我使用axios-auth-refresh 自动刷新访问令牌。)

我正在使用基于 JWT 的身份验证流程来处理后端 API 请求的应用。一般流程运行良好;登录后,用户将获得一个长期刷新令牌和短期访问令牌。使用 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() 块中的错误对象转储。

实际文件位于 GitHub here,尽管它与上面的工作版本略有不同。主要是,在 GH 版本中,钩子调用 axios.post("token").then(...) 在上面我进行更明确的调用以添加 skipAuthRefresh 参数。添加它让我在控制台中获得了更详细的错误跟踪,但我仍然没有通过catch() 捕捉到 401 响应。

我已经尝试了所有我能想到的东西......有什么东西会跳出来作为我缺少的东西吗?

兰迪

(已编辑以确保 GitHub 链接指向存在问题的文件版本。)

【问题讨论】:

    标签: javascript axios jwt


    【解决方案1】:

    自从发布这篇文章以来,我已经设法解决了这个问题并提出了一个可行的解决方案。

    解决方案的关键事实上在于使用不同的 axios 实例来调用更新刷新令牌。我创建了第二个模块来封装第二个 axios 实例,该实例不会获取由axios-auth-refresh 模块创建的拦截器。在解决了最初导致的一些无意的循环依赖问题之后,我发现当刷新令牌本身过时或丢失时,我可以看到 axios 抛出异常。

    (有趣的是,这导致了另一个问题:一旦我发现刷新令牌不再有效,我需要注销用户并让他们返回登录屏幕。因为这个应用程序是一个 React 应用程序, 身份验证是使用自定义钩子处理的, 只能在组件内调用. 但是, 我已将所有 API 调用抽象到一个非 React 模块中, 以便我可以封装诸如添加 Authorization 标头之类的东西,基本 URL 等。在那个级别我无法运行 auth 挂钩来访问注销逻辑。我通过在我用于的查询对象(react-query 对象)上放置一个默认的onError 处理程序来解决这个问题所有 API 调用。)

    【讨论】:

      【解决方案2】:

      我在 this SO answerRequest 类的基础上刷新令牌并处理刷新失败。

      现在我的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;
      
      
      

      【讨论】:

        【解决方案3】:

        我的问题是,当刷新令牌过期时,我无法捕捉 错误并将用户重定向以重新进行身份验证。我没有尝试过 捕获错误。自动刷新挂钩的(当前)代码是:

        如果访问令牌过期,您的 api 的返回码是什么?

        如果不是401(默认)需要配置,见exanoke 403:

        createAuthRefreshInterceptor(axios, refreshAuthLogic, {
          statusCodes: [ 401, 403 ] // default: [ 401 ]
        });
        

        【讨论】:

        • 谢谢,但这不是这里的问题(我的后端仅在授权错误时返回 401)。我需要回答我自己的问题,因为我已经找到了解决方案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-03
        • 2019-08-21
        • 2020-10-01
        • 2020-12-05
        • 1970-01-01
        • 2021-08-23
        相关资源
        最近更新 更多