Comment utiliser Redux pour rafraîchir JWT token?

notre application React Native Redux utilise des tokens JWT pour l'authentification. Il y a beaucoup d'actions qui nécessitent de tels tokens et beaucoup d'entre elles sont lancées simultanément, par exemple quand app charge.

E. G.

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

loadProfile et loadAssets require JWT. Nous avons enregistrer le jeton dans l'état et AsyncStorage. Ma question Est de savoir comment gérer l'expiration du jeton.

à l'origine, j'allais utiliser middleware pour gérer token d'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);
};

}

le problème que j'ai rencontré était que le rafraîchissement du jeton se produira pour les deux loadProfile et loadAssets actions parce qu'au moment où ils sont expédition le jeton sera expiré. Idéalement, je voudrais "mettre en pause" les actions qui nécessitent une authentification jusqu'à ce que le token soit rafraîchi. Est-il un moyen de le faire avec middleware?

35
demandé sur lanan 2016-04-30 01:40:54

3 réponses

j'ai trouvé un moyen de résoudre ce problème. Je ne suis pas sûr qu'il s'agisse d'une approche fondée sur les meilleures pratiques et il y a probablement des améliorations à y apporter.

mon idée originale reste: JWT refresh est dans le middleware. Ce middleware doit passer avant!--3--> si thunk est utilisé.

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

puis dans le code middleware nous vérifions si le token est expiré avant toute action async. Si elle est Expirée, nous vérifions aussi si nous sommes déjà en train de rafraîchir le jeton -- pour pouvoir pour avoir un tel chèque nous ajoutons la promesse pour le jeton frais à l'état.

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().then(() => next(action));
                    } else {
                        return getState().auth.freshTokenPromise.then(() => next(action));
                    }
                }
            }
        }
        return next(action);
    };
}

la partie La plus importante est refreshToken fonction. Cette fonction doit envoyer l'action quand le token est rafraîchi de sorte que l'état contiendra la promesse pour le nouveau token. De cette façon, si nous envoyons plusieurs actions async qui utilisent simultanément token auth, le token ne sera rafraîchi qu'une seule fois.

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;
}

je me rends compte que c'est assez compliqué. Je suis aussi un peu inquiet au sujet de le dispatching des actions dans refreshToken ce qui n'est pas une action en soi. S'il vous plaît me faire savoir de toute autre approche que vous connaissez que les poignées expirant JWT token avec redux.

24
répondu lanan 2016-05-02 15:39:57

au lieu d '"attendre" qu'une action se termine, vous pouvez plutôt garder une variable de stockage pour savoir si vous êtes toujours en train de récupérer des tokens:

réducteur D'échantillon

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

Maintenant l'action du créateur:

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());
       }
    };
}

ceci est appelé lorsque le composant est monté. Si la clé auth est périmée, elle envoie une action à set fetching à true et rafraîchir le token. Notez que nous n'allons pas charger le profil ou les actifs encore.

Nouveau composant:

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()
    } 
}

notez que maintenant vous tentez de charger vos objets sur mount mais aussi dans certaines conditions lors de la réception des accessoires (ceci sera appelé lorsque le magasin change pour que nous puissions garder fetching là) quand le fetch initial échoue, il déclenche le refreshToken. Lorsque cela sera fait, il définira le nouveau token dans la mémoire, mettant à jour le composant et appelant donc componentWillReceiveProps. Si ce n'est pas encore l'extraction (pas sûr de cette vérification est nécessaire), il va charger des choses.

17
répondu ZekeDroid 2016-05-02 15:47:44

j'ai fait un simple wrapper autour de redux-api-middleware pour reporter les actions et rafraîchir le jeton d'accès.

middleware.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();

je garde les tokens dans l'état, et j'utilise un helper simple pour injecter le token Acess dans les en-têtes de requête

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

redux-api-middleware actions reste presque inchangé

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
      ]
  }
})

j'ai écrit le article et partagé l' exemple de projet, qui montre JWT refresh token workflow en action

4
répondu kmmbvnr 2017-09-02 01:12:50