Reputation: 1886
I am new to React Native and one of the first obstacles I am running into is refreshing my access token when it expires. The basic implementation is such that there is an access token and a refresh token. The access token expires and the refresh token is sent within the Authorization header to refresh the access token. Based on this SO post I implemented a Middleware that checks during each API call to see if our access token is expiring soon. If so, it kicks off an action creator which makes API calls to refresh and confirm the new access token. I've only shown the refresh call here, as to not overcomplicate things.
The PROBLEM is that the same middleware that detects the need to refresh is also hit by the refresh call itself, and this leads to an infinite loop and eventually exceeding the call stack size. I was wondering if anybody is able to help me figure out a way around this, or tell me I'm being dumb and offer a better overall solution. Any guidance is much appreciated. Thank you!
function auth({ dispatch, getState }) {
return next => action => {
if (typeof action === 'function') {
// Check expiration of our token
if (tokenIsExpired(getState)) {
console.log('Our access token will expire in less than 30s, refreshing');
// Make sure we are not already refreshing the access token
if (!getState().core.refreshTokenIsLoading) {
return next(dispatch(refreshAccessTokenFlow())).then(() => next(action));
} else {
// Take promise from state and act after it
return getState().core.refreshAccessTokenFlow.then(() => next(action));
}
}
}
return next(action);
}
}
export function refreshAccessTokenFlow() {
return (dispatch) => {
return dispatch(refreshAccessToken())
.then((didRefresh) => {
console.log('refresh access token returned');
return Promise.resolve();
})
.catch(error => {
console.log('refresh access token failed: ' + error);
return Promise.reject(error);
});
}
}
Upvotes: 0
Views: 949
Reputation: 1654
You could for example pass in metadata in the actions that require the access token and afterwards in the middleware check or you need to validate the token based on that metadata.
Action file
// My Team
export function fetchStuffWithOAuthStart(userId) {
return {
type: USER_FETCH_STUFF_WITHOUT_OAUTH_START,
payload: { userId },
meta: {
oauth: true,
}
};
Middleware
function auth({ dispatch, getState }) {
return next => action => {
if (typeof action === 'function') {
// Check here or you need to validate the token
if (!action.meta || !action.meta.oauth) {
return next(action);
}
// Check expiration of our token
if (tokenIsExpired(getState)) {
console.log('Our access token will expire in less than 30s, refreshing');
// Make sure we are not already refreshing the access token
if (!getState().core.refreshTokenIsLoading) {
return next(dispatch(refreshAccessTokenFlow())).then(() => next(action));
} else {
// Take promise from state and act after it
return getState().core.refreshAccessTokenFlow.then(() => next(action));
}
}
}
return next(action);
}
}
export function refreshAccessTokenFlow() {
return (dispatch) => {
return dispatch(refreshAccessToken())
.then((didRefresh) => {
console.log('refresh access token returned');
return Promise.resolve();
})
.catch(error => {
console.log('refresh access token failed: ' + error);
return Promise.reject(error);
});
}
}
Upvotes: 1