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
How to use Redux to refresh JWT token?
提问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 loadProfile
and loadAssets
require JWT. We save the token in the state and AsyncStorage
. My question is how to handle token expiration.
双方loadProfile
并loadAssets
要求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 loadProfile
and loadAssets
actions 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?
我遇到的问题是令牌的刷新将同时用于loadProfile
和loadAssets
动作,因为在它们被分派时令牌将过期。理想情况下,我想“暂停”需要身份验证的操作,直到刷新令牌。有没有办法用中间件做到这一点?
采纳答案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 thunk
if thunk
is 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 refreshToken
function. 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 refreshToken
which 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 fetching
to 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 fetching
there) 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-middleware
to 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-middleware
actions 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