如何在不中断异步流程的情况下在 redux 中自动刷新 JWT?
How do I auto-refresh a JWT in redux without breaking async flow?
高级描述
我有一个使用 Google Oauth 的 React/redux/electron 应用程序。我希望能够在访问令牌过期时自动刷新它。我对此进行了研究并使用中间件半成功地解决了它,但我的解决方案在某些情况下会出错。
我实现了一个刷新中间件,每个 API 操作都会 运行 刷新。它检查访问令牌是否已过期或即将过期。如果是这样,它不会分派它收到的操作,而是分派令牌刷新操作并将任何其他操作排队,直到收到新的访问令牌。之后,它会调度其队列中的所有操作。
但是,我的动作创作者之一看起来像这样:
function queryThreads(params) {
return async (dispatch) => {
const threads = await dispatch(fetchThreads(params))
const newPageToken = threads.payload.nextPageToken
}
}
当刷新中间件没有 运行 因为令牌没有过期时,threads.payload
将在此处定义,一切都会按预期工作。
但是,当刷新中间件执行 运行 时,threads.payload
将是 undefined
,因为 dispatch
似乎使用令牌刷新操作的值来解析,而不是比 fetchThreads
动作。
如何确保令牌得到刷新(并在 state
/localStorage
中更新),fetchThreads
与更新后的令牌一起发送,以及 threads
变量被分配给正确 Promise 的解析值?
项目代码链接
This is my refresh middleware. It was inspired by this article by kmmbvnr.
This is the token refresh action creator.
This is the line in my queryThreads action creator 当令牌必须刷新时抛出(threads.payload
是 undefined
)。
看来我已经通过重写刷新中间件解决了这个问题:
function createRefreshMiddleware() {
const postponedRSAAs = [];
return ({ dispatch, getState }) => {
const rsaaMiddleware = apiMiddleware({ dispatch, getState });
return next => action => {
if (isRSAA(action)) {
try {
const auth = JSON.parse(localStorage.getItem('auth'));
const { refresh_token: refreshToken } = auth;
const expirationTime = jwtDecode(auth.id_token).exp * 1000;
const isAccessTokenExpiring =
moment(expirationTime) - moment() < 300000;
if (refreshToken && isAccessTokenExpiring) {
postponedRSAAs.push(action);
if (postponedRSAAs.length === 1) {
return rsaaMiddleware(next)(
dispatch(() => attemptTokenRefresh(refreshToken))
).then(() => {
const postponedRSAA = postponedRSAAs.pop();
return dispatch(postponedRSAA);
});
}
return rsaaMiddleware(next)(action);
}
return rsaaMiddleware(next)(action);
} catch (e) {
console.log(e);
return next(action);
}
}
return next(action);
};
};
}
export default createRefreshMiddleware();
现在推迟的动作将始终与令牌刷新动作链接在一起,所以我们不存在原始 promise 解析为错误值的问题;而且更简洁。
高级描述
我有一个使用 Google Oauth 的 React/redux/electron 应用程序。我希望能够在访问令牌过期时自动刷新它。我对此进行了研究并使用中间件半成功地解决了它,但我的解决方案在某些情况下会出错。
我实现了一个刷新中间件,每个 API 操作都会 运行 刷新。它检查访问令牌是否已过期或即将过期。如果是这样,它不会分派它收到的操作,而是分派令牌刷新操作并将任何其他操作排队,直到收到新的访问令牌。之后,它会调度其队列中的所有操作。
但是,我的动作创作者之一看起来像这样:
function queryThreads(params) {
return async (dispatch) => {
const threads = await dispatch(fetchThreads(params))
const newPageToken = threads.payload.nextPageToken
}
}
当刷新中间件没有 运行 因为令牌没有过期时,threads.payload
将在此处定义,一切都会按预期工作。
但是,当刷新中间件执行 运行 时,threads.payload
将是 undefined
,因为 dispatch
似乎使用令牌刷新操作的值来解析,而不是比 fetchThreads
动作。
如何确保令牌得到刷新(并在 state
/localStorage
中更新),fetchThreads
与更新后的令牌一起发送,以及 threads
变量被分配给正确 Promise 的解析值?
项目代码链接
This is my refresh middleware. It was inspired by this article by kmmbvnr.
This is the token refresh action creator.
This is the line in my queryThreads action creator 当令牌必须刷新时抛出(threads.payload
是 undefined
)。
看来我已经通过重写刷新中间件解决了这个问题:
function createRefreshMiddleware() {
const postponedRSAAs = [];
return ({ dispatch, getState }) => {
const rsaaMiddleware = apiMiddleware({ dispatch, getState });
return next => action => {
if (isRSAA(action)) {
try {
const auth = JSON.parse(localStorage.getItem('auth'));
const { refresh_token: refreshToken } = auth;
const expirationTime = jwtDecode(auth.id_token).exp * 1000;
const isAccessTokenExpiring =
moment(expirationTime) - moment() < 300000;
if (refreshToken && isAccessTokenExpiring) {
postponedRSAAs.push(action);
if (postponedRSAAs.length === 1) {
return rsaaMiddleware(next)(
dispatch(() => attemptTokenRefresh(refreshToken))
).then(() => {
const postponedRSAA = postponedRSAAs.pop();
return dispatch(postponedRSAA);
});
}
return rsaaMiddleware(next)(action);
}
return rsaaMiddleware(next)(action);
} catch (e) {
console.log(e);
return next(action);
}
}
return next(action);
};
};
}
export default createRefreshMiddleware();
现在推迟的动作将始终与令牌刷新动作链接在一起,所以我们不存在原始 promise 解析为错误值的问题;而且更简洁。