59 votes

Comment utiliser Redux pour actualiser le jeton JWT?

Notre Réagir Natif Redux application utilise JWT les jetons d'authentification. Il y a beaucoup d'actions qui nécessitent de tels jetons et beaucoup d'entre eux sont envoyés simultanément par exemple, lors de l'application de la charge.

E. g.

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

Les deux loadProfile et loadAssets besoin de JWT. Nous avons enregistrer le jeton de l'état et de l' AsyncStorage. Ma question est comment gérer jeton d'expiration.

A l'origine, j'allais à l'utilisation du middleware pour la manipulation du jeton 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 l'actualisation du jeton qui va se passer pour les deux loadProfile et loadAssets d'actions parce qu'au moment où ils sont expédition le jeton sera expiré. Idéalement, je voudrais à "pause" des actions qui nécessitent une authentification jusqu'à ce que le jeton est actualisé. Est-il un moyen de le faire avec middleware?

53voto

Shvetusya Points 621

J'ai trouvé un moyen de résoudre ce problème. Je ne suis pas sûr si c'est l'approche des meilleures pratiques et il y a probablement certaines améliorations qui pourraient y être apportées.

Mon idée de départ reste: JWT d'actualisation est dans le middleware. Que le middleware doit venir avant thunk si thunk est utilisé.

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

Puis dans le middleware de code, nous vérifions pour voir si le token est expiré avant toute action asynchrone. S'il est expiré, nous aussi vérifier si nous sommes déjà sont rafraîchissantes, le jeton -- être en mesure d'avoir ce contrôle nous ajouter promesse pour les frais de jeton à 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 des besoins de l'expédition d'action lorsque le jeton est en cours d'actualisation afin que l'etat va contenir la promesse du nouveau jeton. De cette façon, si nous expédions plusieurs actions asynchrones que l'utilisation de jeton d'authentification simultanément le jeton sera mise à jour 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 dispatching d'actions en refreshToken ce qui n'est pas l'action elle-même. S'il vous plaît laissez-moi savoir de toute autre approche, vous savez que les poignées expirant JWT jeton avec redux.

22voto

ZekeDroid Points 706

Au lieu de "en attente" pour une action à la fin, vous pourriez tenir un magasin variable pour savoir si vous êtes toujours aller chercher des jetons:

Réducteur d'échantillonnage

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

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

Ce qui est appelé lorsque le composant monté. Si la clé auth est périmé, il enverra une action en fetching de vrai et aussi actualiser le jeton. 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()
    } 
}

Remarquez que maintenant vous tentez de charger vos choses sur le mont, mais aussi, sous certaines conditions, lors de la réception d'accessoires (ce sera appelée lorsque le magasin de modifications afin que nous puissions conserver fetching y) Lors de l'extraction initiale échoue, il va déclencher l' refreshToken. Quand cela est fait, il va définir le nouveau jeton dans le magasin, la mise à jour du composant et donc appelant componentWillReceiveProps. Si ce n'est pas encore l'extraction (pas sûr de cette vérification est nécessaire), il va charger des choses.

8voto

kmmbvnr Points 129

J'ai fait un simple wrapper autour de redux-api-middleware de reporter les actions et les actualiser 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();

J'ai garder les jetons de l'état, et l'utilisation d'une simple aide pour injecter de l'Accès jeton en-têtes de la requête

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

Donc, redux-api-middleware d'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 l' article et partagé le projet exemple, qui montre JWT jeton d'actualisation de flux de travail dans l'action

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X