Javascript 如何使用 Redux 刷新 JWT 令牌?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36948557/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 19:45:18  来源:igfitidea点击:

How to use Redux to refresh JWT token?

javascriptreactjsreact-nativereduxjwt

提问by lanan

Our React Native Redux app uses JWT tokens for authentication. There are many actions that require such tokens and a lot of them are dispatched simultaneously e.g. when app loads.

我们的 React Native Redux 应用程序使用 JWT 令牌进行身份验证。有许多操作需要此类令牌,​​并且其中许多操作是同时调度的,例如在应用加载时。

E.g.

例如

componentDidMount() {
    dispath(loadProfile());
    dispatch(loadAssets());
    ...
}

Both loadProfileand loadAssetsrequire JWT. We save the token in the state and AsyncStorage. My question is how to handle token expiration.

双方loadProfileloadAssets要求JWT。我们将令牌保存在 state 和 中AsyncStorage。我的问题是如何处理令牌过期。

Originally I was going to use middleware for handling token expiration

最初我打算使用中间件来处理令牌过期

// jwt-middleware.js

export function refreshJWTToken({ dispatch, getState }) {

  return (next) => (action) => {
    if (isExpired(getState().auth.token)) {
      return dispatch(refreshToken())
          .then(() => next(action))
          .catch(e => console.log('error refreshing token', e));
    }
    return next(action);
};

}

}

The problem that I ran into was that refreshing of the token will happen for both loadProfileand loadAssetsactions because at the time when they are dispatch the token will be expired. Ideally I would like to "pause" actions that require authentication until the token is refreshed. Is there a way to do that with middleware?

我遇到的问题是令牌的刷新将同时用于loadProfileloadAssets动作,因为在它们被分派时令牌将过期。理想情况下,我想“暂停”需要身份验证的操作,直到刷新令牌。有没有办法用中间件做到这一点?

采纳答案by lanan

I found a way to solve this. I am not sure if this is best practice approach and there are probably some improvements that could be made to it.

我找到了解决这个问题的方法。我不确定这是否是最佳实践方法,并且可能可以对其进行一些改进。

My original idea stays: JWT refresh is in the middleware. That middleware has to come before thunkif thunkis used.

我最初的想法仍然是:JWT 刷新在中间件中。thunk如果thunk使用该中间件,则必须在该中间件之前出现。

...
const createStoreWithMiddleware = applyMiddleware(jwt, thunk)(createStore);

Then in the middleware code we check to see if token is expired before any async action. If it is expired we also check if we are already are refreshing the token -- to be able to have such check we add promise for fresh token to the state.

然后在中间件代码中,我们检查令牌是否在任何异步操作之前过期。如果它已过期,我们还会检查我们是否已经在刷新令牌——为了能够进行这样的检查,我们向状态添加了对新鲜令牌的承诺。

import { refreshToken } from '../actions/auth';

export function jwt({ dispatch, getState }) {

    return (next) => (action) => {

        // only worry about expiring token for async actions
        if (typeof action === 'function') {

            if (getState().auth && getState().auth.token) {

                // decode jwt so that we know if and when it expires
                var tokenExpiration = jwtDecode(getState().auth.token).<your field for expiration>;

                if (tokenExpiration && (moment(tokenExpiration) - moment(Date.now()) < 5000)) {

                    // make sure we are not already refreshing the token
                    if (!getState().auth.freshTokenPromise) {
                        return refreshToken(dispatch).then(() => next(action));
                    } else {
                        return getState().auth.freshTokenPromise.then(() => next(action));
                    }
                }
            }
        }
        return next(action);
    };
}

The most important part is refreshTokenfunction. That function needs to dispatch action when token is being refreshed so that the state will contain the promise for the fresh token. That way if we dispatch multiple async actions that use token auth simultaneously the token gets refreshed only once.

最重要的部分是refreshToken功能。该函数需要在令牌刷新时分派操作,以便状态将包含对新令牌的承诺。这样,如果我们分派多个同时使用令牌身份验证的异步操作,令牌只会刷新一次。

export function refreshToken(dispatch) {

    var freshTokenPromise = fetchJWTToken()
        .then(t => {
            dispatch({
                type: DONE_REFRESHING_TOKEN
            });

            dispatch(saveAppToken(t.token));

            return t.token ? Promise.resolve(t.token) : Promise.reject({
                message: 'could not refresh token'
            });
        })
        .catch(e => {

            console.log('error refreshing token', e);

            dispatch({
                type: DONE_REFRESHING_TOKEN
            });
            return Promise.reject(e);
        });



    dispatch({
        type: REFRESHING_TOKEN,

        // we want to keep track of token promise in the state so that we don't try to refresh
        // the token again while refreshing is in process
        freshTokenPromise
    });

    return freshTokenPromise;
}

I realize that this is pretty complicated. I am also a bit worried about dispatching actions in refreshTokenwhich is not an action itself. Please let me know of any other approach you know that handles expiring JWT token with redux.

我意识到这非常复杂。我也有点担心调度refreshToken不是动作本身的动作。请让我知道您知道的使用 redux 处理过期 JWT 令牌的任何其他方法。

回答by ZekeDroid

Instead of "waiting" for an action to finish, you could instead keep a store variable to know if you're still fetching tokens:

与其“等待”某个动作完成,您还可以保留一个 store 变量来了解您是否仍在获取令牌:

Sample reducer

样品减速器

const initialState = {
    fetching: false,
};
export function reducer(state = initialState, action) {
    switch(action.type) {
        case 'LOAD_FETCHING':
            return {
                ...state,
                fetching: action.fetching,
            }
    }
}

Now the action creator:

现在动作创建者:

export function loadThings() {
    return (dispatch, getState) => {
        const { auth, isLoading } = getState();

        if (!isExpired(auth.token)) {
            dispatch({ type: 'LOAD_FETCHING', fetching: false })
            dispatch(loadProfile());
            dispatch(loadAssets());
       } else {
            dispatch({ type: 'LOAD_FETCHING', fetching: true })
            dispatch(refreshToken());
       }
    };
}

This gets called when the component mounted. If the auth key is stale, it will dispatch an action to set fetchingto true and also refresh the token. Notice that we aren't going to load the profile or assets yet.

这在组件安装时被调用。如果 auth 密钥过时,它将分派一个动作以设置fetching为 true 并刷新令牌。请注意,我们还不会加载配置文件或资产。

New component:

新组件:

componentDidMount() {
    dispath(loadThings());
    // ...
}

componentWillReceiveProps(newProps) {
    const { fetching, token } = newProps; // bound from store

    // assuming you have the current token stored somewhere
    if (token === storedToken) {
        return; // exit early
    }

    if (!fetching) {
        loadThings()
    } 
}

Notice that now you attempt to load your things on mount but also under certain conditions when receiving props (this will get called when the store changes so we can keep fetchingthere) When the initial fetch fails, it will trigger the refreshToken. When that is done, it'll set the new token in the store, updating the component and hence calling componentWillReceiveProps. If it's not still fetching (not sure this check is necessary), it will load things.

请注意,现在您尝试在 mount 上加载您的东西,但在接收道具时也在某些条件下加载(这将在商店更改时被调用,以便我们可以保留fetching在那里)当初始获取失败时,它将触发refreshToken. 完成后,它将在商店中设置新令牌,更新组件并因此调用componentWillReceiveProps. 如果它仍然没有获取(不确定这个检查是否必要),它会加载东西。

回答by kmmbvnr

I made a simple wrapper around redux-api-middlewareto postpone actions and refresh access token.

我做了一个简单的包装器redux-api-middleware来推迟操作和刷新访问令牌。

middleware.js

中间件.js

import { isRSAA, apiMiddleware } from 'redux-api-middleware';

import { TOKEN_RECEIVED, refreshAccessToken } from './actions/auth'
import { refreshToken, isAccessTokenExpired } from './reducers'


export function createApiMiddleware() {
  const postponedRSAAs = []

  return ({ dispatch, getState }) => {
    const rsaaMiddleware = apiMiddleware({dispatch, getState})

    return (next) => (action) => {
      const nextCheckPostoned = (nextAction) => {
          // Run postponed actions after token refresh
          if (nextAction.type === TOKEN_RECEIVED) {
            next(nextAction);
            postponedRSAAs.forEach((postponed) => {
              rsaaMiddleware(next)(postponed)
            })
          } else {
            next(nextAction)
          }
      }

      if(isRSAA(action)) {
        const state = getState(),
              token = refreshToken(state)

        if(token && isAccessTokenExpired(state)) {
          postponedRSAAs.push(action)
          if(postponedRSAAs.length === 1) {
            return  rsaaMiddleware(nextCheckPostoned)(refreshAccessToken(token))
          } else {
            return
          }
        }

        return rsaaMiddleware(next)(action);
      }
      return next(action);
    }
  }
}

export default createApiMiddleware();

I keep tokens in the state, and use a simple helper to inject Acess token into a request headers

我将令牌保留在状态中,并使用一个简单的帮助程序将 Acess 令牌注入请求标头中

export function withAuth(headers={}) {
  return (state) => ({
    ...headers,
    'Authorization': `Bearer ${accessToken(state)}`
  })
}

So redux-api-middlewareactions stays almost unchanged

所以redux-api-middleware行动几乎保持不变

export const echo = (message) => ({
  [RSAA]: {
      endpoint: '/api/echo/',
      method: 'POST',
      body: JSON.stringify({message: message}),
      headers: withAuth({ 'Content-Type': 'application/json' }),
      types: [
        ECHO_REQUEST, ECHO_SUCCESS, ECHO_FAILURE
      ]
  }
})

I wrote the articleand shared the project example, that shows JWT refresh token workflow in action

我写了这篇文章并分享了项目示例,它显示了 JWT 刷新令牌工作流程的实际效果