when node generate expiration javascript reactjs react-native redux jwt

javascript - node - ¿Cómo usar Redux para actualizar el token JWT?



token node js (3)

Nuestra aplicación React Native Redux usa tokens JWT para la autenticación. Hay muchas acciones que requieren tales tokens y muchas de ellas se envían simultáneamente, por ejemplo, cuando se carga una aplicación.

P.ej

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

Tanto loadProfile como loadAssets requieren JWT. Guardamos el token en el estado y AsyncStorage . Mi pregunta es cómo manejar la caducidad del token.

Originalmente iba a usar middleware para manejar la caducidad del token

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

}

El problema con el que me encontré fue que la actualización del token ocurrirá para las acciones loadProfile y loadAssets porque en el momento en que se despachan, el token loadAssets . Idealmente, me gustaría "pausar" las acciones que requieren autenticación hasta que se actualice el token. ¿Hay alguna manera de hacerlo con middleware?


En lugar de "esperar" que termine una acción, puedes guardar una variable de tienda para saber si todavía estás obteniendo tokens:

Reductor de muestra

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

Ahora el creador de acciones:

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

Se llama cuando se monta el componente. Si la clave de autenticación está obsoleta, enviará una acción para establecer la fetching en verdadero y también actualizará el token. Tenga en cuenta que todavía no vamos a cargar el perfil o los activos.

Nuevo componente:

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

Tenga en cuenta que ahora intenta cargar sus cosas en mount pero también bajo ciertas condiciones cuando recibe props (se llamará a esto cuando cambie la tienda para que podamos seguir fetching allí). Cuando la búsqueda inicial falla, activará el refreshToken . Cuando lo haya hecho, configurará el nuevo token en la tienda, actualizando el componente y, por lo tanto, llamando a componentWillReceiveProps . Si aún no está buscando (no estoy seguro de que este control sea necesario), cargará cosas.


Encontré una manera de resolver esto. No estoy seguro si este es el enfoque de mejores prácticas y es probable que se puedan hacer algunas mejoras.

Mi idea original permanece: la actualización de JWT está en el middleware. Ese middleware tiene que venir antes del thunk si se usa thunk .

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

Luego, en el código de middleware, verificamos si el token está vencido antes de cualquier acción asíncrona. Si ha caducado, también verificamos si ya estamos actualizando el token: para poder tener dicho control, le agregamos la promesa de un token nuevo al estado.

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 parte más importante es la función refreshToken . Esa función necesita despachar la acción cuando el token se actualiza para que el estado contenga la promesa para el token nuevo. De esta forma, si distribuimos varias acciones asíncronas que usan la autenticación de token simultáneamente, el token se actualiza solo una vez.

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

Me doy cuenta de que esto es bastante complicado. También estoy un poco preocupado por el envío de acciones en refreshToken que no es una acción en sí misma. Indíqueme cualquier otro enfoque que conozca que maneje el token JWT que expira con redux.


Hice un envoltorio simple alrededor redux-api-middleware para posponer acciones y actualizar el token de acceso.

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

Mantengo tokens en el estado y uso un simple helper para inyectar el token de Acess en los encabezados de las solicitudes

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

Así que las acciones redux-api-middleware mantienen casi sin cambios

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

Escribí el article y compartí el ejemplo del proyecto , que muestra el flujo de trabajo de token de actualización de JWT en acción.