Te damos la solución a esta incógnita, o por lo menos eso pensamos. Si continuas con dudas coméntalo, que sin dudar
Solución:
Encontré una manera de resolver esto. No estoy seguro de si este es el enfoque de mejores prácticas y probablemente haya algunas mejoras que podrían hacerse.
Mi idea original se mantiene: la actualización de JWT está en el middleware. Ese middleware tiene que venir antes thunk
si thunk
se usa
...
const createStoreWithMiddleware = applyMiddleware(jwt, thunk)(createStore);
Luego, en el código de middleware, verificamos si el token ha caducado antes de cualquier acción asíncrona. Si está vencido, también verificamos si ya estamos actualizando el token; para poder tener tal verificación, 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).;
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);
;
La parte más importante es refreshToken
función. Esa función debe enviar una acción cuando se actualice el token para que el estado contenga la promesa del token nuevo. De esa manera, si despachamos varias acciones asíncronas que usan 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 me preocupa un poco despachar acciones en refreshToken
que no es una acción en sí misma. Infórmeme sobre cualquier otro enfoque que conozca que maneje la expiración del token JWT con redux.
En lugar de “esperar” a que termine una acción, podría mantener una variable de tienda para saber si todavía está 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 la acción:
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());
;
Esto se llama cuando se monta el componente. Si la autorización key está obsoleto, enviará una acción para establecer fetching
para true y también actualizar el token. Tenga en cuenta que aún 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 la montura, pero también bajo ciertas condiciones cuando recibe accesorios (esto se llamará cuando la tienda cambie para que podamos seguir fetching
allí) Cuando la recuperación inicial falla, activará la refreshToken
. Cuando termine, configurará el nuevo token en la tienda, actualizará el componente y, por lo tanto, llamará componentWillReceiveProps
. Si aún no se está recuperando (no estoy seguro de que esta verificación sea necesaria), cargará las cosas.
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 nextCheckPostponed = (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(nextCheckPostponed)(refreshAccessToken(token))
else
return
return rsaaMiddleware(next)(action);
return next(action);
export default createApiMiddleware();
Mantengo los tokens en el estado y uso un asistente simple para inyectar el token Acess en los encabezados de una solicitud
export function withAuth(headers=)
return (state) => (
...headers,
'Authorization': `Bearer $accessToken(state)`
)
Entonces redux-api-middleware
las acciones permanecen 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 artículo y compartí el ejemplo del proyecto, que muestra el flujo de trabajo del token de actualización de JWT en acción