当 jwt 刷新令牌未过期时,React Native 应用程序注销

Posted

技术标签:

【中文标题】当 jwt 刷新令牌未过期时,React Native 应用程序注销【英文标题】:React Native app logs out when jwt refresh token is not expired 【发布时间】:2021-09-14 05:41:57 【问题描述】:

我正在使用 JWT 令牌来验证我的 API 请求。访问令牌在 1 分钟后过期,刷新令牌在 1 年内过期。访问令牌过期后,会发送一个带有刷新令牌的 API 请求以获取一组新令牌。仅当刷新令牌有效且存在于数据库中时才会发送一组新令牌。我正在使用 Axios 拦截器来实现这一点。一段时间以来,一切似乎都可以正常工作。但是,即使刷新令牌有效并且数据库中确实存在,它也会将我注销。我假设我在 Axios 拦截器中遗漏了一些东西,或者与异步函数有关。

服务器端verifyRefreshToken函数中的错误日志“代码不匹配”,客户端updateToken函数中的“此处错误”。

客户端代码 API.js

// Response interceptor for API calls
API.interceptors.response.use((response) => 
  return response
, async (error) => 
    // reject promise if network error
    if (!error.response) 
      console.log("Network Error");
      return Promise.reject(error);
    

    const originalRequest = error.config;
    console.log(store.getState().auth)
    // if access token is expired
    if (error.response.status === 403 && error.response.data.message == "token expired") 
      // var refreshToken = await getRefreshToken() // get refresh token from local storage
      var refreshToken = await store.getState().auth.refreshToken

      // restore tokens using refresh token
      await store.dispatch(await updateToken(refreshToken)) // get new set of tokens from server and store tokens in redux state
      // var newAccessToken = await getToken() // get token from local storage
      var newAccessToken = await store.getState().auth.accessToken

      if(newAccessToken != null)
        originalRequest.headers["Authorization"] = `Bearer $newAccessToken`;
        return API(originalRequest)
      
      return Promise.reject(error);
    

    // if refresh token is expired or does not match
    if (error.response.status === 403 && error.response.data.message == "false token") 
      socketDisconnect() // disconnect socket connection
      signOut() // remove tokens from local storage
      store.dispatch(logOut()) // set tokens in redux as null
      return Promise.reject(error);
    
  return Promise.reject(error);
);

更新令牌函数

export const updateToken = (rt) => 
  return async (dispatch) => 
    const data = await API.post('/auth/refreshToken', 
        token: rt
      )
      .then(async res => 
        var accessToken = res.data.accessToken
        var refreshToken = res.data.refreshToken

        await storeToken(accessToken) // store access token in local storage
        await storeRefreshToken(refreshToken)  // store refresh token in local storage
        dispatch(restoreToken(accessToken, refreshToken)) // store token in redux state
      )
      .catch(err => 
        console.log("err here" + err) // LOG SHOWS ERROR HERE
      )
  
  

服务器端代码 // /auth/refreshToken

// POST: /api/auth/refreshToken
router.post('/', (req, res) => 
    var  token   = req.body
    if(!token) res.status(403).send("status":false, "message": "false token", "result": "")

    verifyRefreshToken(token)
    .then(async data => 
        var userName = data.userName

        // get new tokens
        var accessToken = await getAccessToken(userName)
        var refreshToken = await getRefreshToken(userName)

        res.json("status":true, "message": "token verified", "accessToken": accessToken, "refreshToken": refreshToken)
    )
    .catch(err => 
        console.log(err);
        res.status(403).send("status":false, "message": "false token", "result": "")
    )
    
);

生成新的刷新令牌

// generate refresh token
const getRefreshToken = (userName) => 
    return new Promise((resolve, reject) =>        
        var secret = process.env.REFRESH_TOKEN_SECRET
        var options =  expiresIn: '1y' 

        jwt.sign(userName,secret , options, (err, token) => 
            if(err) reject("error")
            
            var data = "userName": userName, "token": token

            // delete all expired token from database
            dbQueries.deleteRefreshToken(data, result => 

            )

            // add refresh token to database
            dbQueries.addRefreshToken(data, result => 
                if(result == "success")
                    console.log("added token " + token);
                    resolve(token)
                else
                    reject("failure")
                
            )
        );
    )

验证刷新令牌

// verify access token
const verifyRefreshToken = (token) =>  
    return new Promise((resolve, reject) => 
        var secret = process.env.REFRESH_TOKEN_SECRET
        if(!token) return reject("no token")
        
        jwt.verify(token, secret, (err, user) => 
            if(err)
                return reject(err)
             

            // check if the verified token and token from database matches
            var data = "userName": user.userName
            dbQueries.getRefreshToken(data, result => 
                if(result.length == 0)
                    return reject("no data")  
                
                if(token === result[0].token)
                    resolve(user)
                 else
                    reject("code does not match") // LOGS THIS ERROR
                
            )
            
        )
    )

【问题讨论】:

【参考方案1】:

更新 该错误是由于同时调用多个 API 造成的,并且所有人都请求了新的访问令牌和旧的刷新令牌。我使用link 中的代码解决了这个问题。

【讨论】:

以上是关于当 jwt 刷新令牌未过期时,React Native 应用程序注销的主要内容,如果未能解决你的问题,请参考以下文章

JWT, 为啥需要刷新令牌?

jwt 访问令牌和刷新令牌流

如何在 React.js 应用程序中刷新 JWT 令牌?

如何在jwt中过期时刷新令牌

使用 Angular 和 JWT 令牌持续登录

过期后使用刷新令牌获取访问令牌(JWT)