Node.js 为什么axios拦截器经常失败?我正在尝试获取刷新令牌并重新请求原始请求

Node.js 为什么axios拦截器经常失败?我正在尝试获取刷新令牌并重新请求原始请求,node.js,reactjs,express,axios,jwt,Node.js,Reactjs,Express,Axios,Jwt,我使用访问和刷新令牌,它们都存储在cookie中。我正在使用axios拦截器捕获401错误导致的请求失败,如果是这样,它将获取一个新令牌,然后重试 但似乎有20%的时间,特别是在mobile safari上,即使我有一个有效的刷新令牌,这也会失败,并导致我注销 这很奇怪,因为在后端控制台中,我可以看到一个新的访问和刷新令牌被发送。如果我立即刷新页面,它会让我重新登录 对于前端,对于第一个请求,它将显示401错误,但即使我告诉它尝试多次,它也不会显示任何其他错误。它似乎几乎跳过了重试原始请求,而是

我使用访问和刷新令牌,它们都存储在cookie中。我正在使用axios拦截器捕获401错误导致的请求失败,如果是这样,它将获取一个新令牌,然后重试

但似乎有20%的时间,特别是在mobile safari上,即使我有一个有效的刷新令牌,这也会失败,并导致我注销

这很奇怪,因为在后端控制台中,我可以看到一个新的访问和刷新令牌被发送。如果我立即刷新页面,它会让我重新登录

对于前端,对于第一个请求,它将显示401错误,但即使我告诉它尝试多次,它也不会显示任何其他错误。它似乎几乎跳过了重试原始请求,而是直接进入错误部分。我还可以在“网络”选项卡中看到,它只发出一次请求,这是失败的原始请求

我还可以从后端看到,它从未命中原始的auth中间件,这让我更相信它有时没有尝试原始请求

我同时发送多个请求,如果访问令牌过期,它将导致每个请求获得刷新令牌,我不确定这是否也是一个问题,但它似乎在大多数情况下都有效

我将发布下面的代码,以显示我是如何做到这一点的。我正在运行最新的Axios版本,节点14.5.0和14.7.1,因此一切都是最新的

Axios拦截器代码,可能值得注意的是错误函数在哪里,它说“这可能会转换为异步”,所以我不确定这是否是一个潜在问题:

const axiosNoRetry = axios.create();

axios.interceptors.response.use( (response) => {

    console.log("axios interceptor")
    return response;

}, (error) => {

    const originalRequest = error.config;

    if (originalRequest.ran === 3) {
      console.log("Max retry count", error.config.url);
      return Promise.reject(error);
    }

    console.log("axios retry count", originalRequest.ran);

    if (error.response.status !== 401) {
        console.log("error does not equal 401");
      return new Promise((resolve, reject) => {
        reject(error);
      });
    }

    if (error.config.url === "/user-service/get-token") {
        console.log("error url equal to refresh token route")
      return new Promise((resolve, reject) => {
        reject(error);
      });
    }

    return axiosNoRetry.post("/user-service/get-token").then((cookieResponse) => {

      console.log("cookie status", cookieResponse.status);

      if (cookieResponse.status === 201) {
    
          originalRequest.ran = originalRequest.ran ? originalRequest.ran + 1 : 1;
          return axios(originalRequest);

      } else {
          return Promise.reject(error);
      }

    })
});

export default axios;
这是我的身份验证中间件,它保护/user-service/user路由以检查是否有人登录:

const auth = async(req: RequestType, res: Response, next: NextFunction) => {

    try {

        console.log("\nAUTH NO EMAIL");

        const accessToken = req.cookies["access-token"];

        if (!accessToken) throw new Error("No Access Token");

        const decoded = jwt.verify(accessToken, env.passwordAccess!) as jwtType;

        const user = decoded.user;

        if (!user) throw new Error("No User");

        req.user = user;

        next();

    } catch (e) {
        console.log("\nAuthorization Middleware No Email Verifiaction Error:",         
    e.message);
        res.status(401).send("Error Authenticating");
    }
}

export default auth;
以下是检查是否存在有效刷新令牌的刷新中间件:

const authRefresh = async(req: RequestType, res: Response, next: NextFunction) => {

    try {

        console.log("\nrefresh token auth")

        const refreshToken = req.cookies["refresh-token"];

        if (!refreshToken) throw new Error("No Refresh Token");

        const decoded = jwt.verify(refreshToken, env.passwordRefresh!) as jwtType;  

        const time = decoded.time;
        
        const user = await User.findById(new ObjectID(decoded._id));

        if (!user) throw new Error("No User");

        const encrpytionKey = user.getEncryptionKey();
        const encryptedToken = user.encryptToken(refreshToken, encrpytionKey, 
    decoded.iv);

        let tokenFound = false;
        
        for (let i = 0; i < user.tokens.length; i++) {

            const currentEncryptedToken = user.tokens[i].token;

            if (currentEncryptedToken === encryptedToken) {

                tokenFound = true;
                removeOldTokens(user._id, req.clientIp, time);
                break;
            }
        }

        if (!tokenFound) throw new Error("Refresh Token Not Found");

        req.user = user;

        next();

    } catch (e) {
        console.log("\nAuthorization Refresh Middleware Error:", e.message);
        res.status(401).send("Error Refreshing Token");
    }
}
因此,正如您所看到的,“AUTH NO EMAIL”路由只被调用一次,它将不再被调用,并且刷新令牌将被发送

以下是我在前端控制台中看到的内容:

[Error] Failed to load resource: the server responded with a status of 401 (Unauthorized) (user, line 0)
[Log] axios retry count – undefined (bundle.js, line 323)
[Error] Failed to load resource: the server responded with a status of 401 (Unauthorized) (list, line 0)
[Log] axios retry count – undefined (bundle.js, line 323)
[Log] cookie status – 201 (bundle.js, line 323, x2)
[Log] axios retry count – 1 (bundle.js, line 323)
[Log] cookie status – 201 (bundle.js, line 323)
[Log] axios retry count – 2 (bundle.js, line 323)
[Log] cookie status – 201 (bundle.js, line 323)
[Log] Max retry count – "http://192.168.0.28:3000/user-service/user" (bundle.js, line 323)
[Log] login check error (4) (bundle.js, line 323)
Error: Request failed with status code 401
"Error Authenticating"
undefined
{data: "Error Authenticating", status: 401, statusText: "Unauthorized", headers: Object, config: Object, …}
[Log] axios interceptor (bundle.js, line 323)
我不知道为什么它在底部写着“axios interceptor”,正如你在我上面的代码中看到的,这应该只在请求成功时才显示出来,但它似乎没有成功,因为我再也看不到该请求,并且它将我注销。您可以看到它也返回了一个带有登录检查错误的错误

我不确定是否需要,但我也会发布前端的登录检查代码:

export const startLoginCheck = (currentRoute) => {

    return (dispatch) => {

        axios.get(currentURL+"/user-service/user").then((response) => {

            console.log("USER SERVICE LOGIN CHECK RESPONSE")
    
            const emailVerified = response.data.emailVerified;

            const id = response.data._id;

            env.googleDriveEnabled = response.data.googleDriveEnabled;
            env.s3Enabled = response.data.s3Enabled;
            env.activeSubscription = response.data.activeSubscription;
            env.emailAddress = response.data.email;
            env.name = response.data.name || ""

            if (emailVerified) {
                dispatch(setLoginFailed(false))
                dispatch(login(id))
                history.push(currentRoute);
            } else {
                console.log("Email Not Verified")
                dispatch(setLoginFailed("Unverified Email", 404))
            }

        }).catch((err) => {

            console.log("login check error", err, err.response.data, err.data, 
        err.response);
            // window.localStorage.removeItem("token")
            dispatch(setLoginFailed("Login Expired"))
            // history.push("/login")
        })
    }
}
如果有人有任何想法,我会很高兴听到,这让我发疯

[Error] Failed to load resource: the server responded with a status of 401 (Unauthorized) (user, line 0)
[Log] axios retry count – undefined (bundle.js, line 323)
[Error] Failed to load resource: the server responded with a status of 401 (Unauthorized) (list, line 0)
[Log] axios retry count – undefined (bundle.js, line 323)
[Log] cookie status – 201 (bundle.js, line 323, x2)
[Log] axios retry count – 1 (bundle.js, line 323)
[Log] cookie status – 201 (bundle.js, line 323)
[Log] axios retry count – 2 (bundle.js, line 323)
[Log] cookie status – 201 (bundle.js, line 323)
[Log] Max retry count – "http://192.168.0.28:3000/user-service/user" (bundle.js, line 323)
[Log] login check error (4) (bundle.js, line 323)
Error: Request failed with status code 401
"Error Authenticating"
undefined
{data: "Error Authenticating", status: 401, statusText: "Unauthorized", headers: Object, config: Object, …}
[Log] axios interceptor (bundle.js, line 323)
export const startLoginCheck = (currentRoute) => {

    return (dispatch) => {

        axios.get(currentURL+"/user-service/user").then((response) => {

            console.log("USER SERVICE LOGIN CHECK RESPONSE")
    
            const emailVerified = response.data.emailVerified;

            const id = response.data._id;

            env.googleDriveEnabled = response.data.googleDriveEnabled;
            env.s3Enabled = response.data.s3Enabled;
            env.activeSubscription = response.data.activeSubscription;
            env.emailAddress = response.data.email;
            env.name = response.data.name || ""

            if (emailVerified) {
                dispatch(setLoginFailed(false))
                dispatch(login(id))
                history.push(currentRoute);
            } else {
                console.log("Email Not Verified")
                dispatch(setLoginFailed("Unverified Email", 404))
            }

        }).catch((err) => {

            console.log("login check error", err, err.response.data, err.data, 
        err.response);
            // window.localStorage.removeItem("token")
            dispatch(setLoginFailed("Login Expired"))
            // history.push("/login")
        })
    }
}