Last active
September 5, 2023 22:29
-
-
Save alfonmga/9602085094651c03cd2e270da9b2e3f7 to your computer and use it in GitHub Desktop.
Apollo refresh auth token link. It tries to refresh the user access token on the fly when API throws out an UNAUTHENTICATED error. If multiple requests fail at the same time, it queues them to re-try them later if we are able to get a new access token, otherwise we log out the user and redirect him to the login page.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { onError } from 'apollo-link-error'; | |
import { Observable } from 'apollo-link'; | |
import { buildAuthHeader } from 'utils/requests'; | |
import { getProvider as getGlobalProvider } from 'GlobalState'; | |
let isFetchingToken = false; | |
let tokenSubscribers = []; | |
function subscribeTokenRefresh(cb) { | |
tokenSubscribers.push(cb); | |
} | |
function onTokenRefreshed(err) { | |
tokenSubscribers.map(cb => cb(err)); | |
} | |
/* eslint-disable consistent-return */ | |
const refreshAuthTokenLink = () => | |
onError( | |
({ graphQLErrors, networkError, operation, response, forward }) => | |
new Observable(async observer => { | |
if (graphQLErrors) { | |
graphQLErrors.map(async ({ extensions }, index) => { | |
switch (extensions.code) { | |
case 'UNAUTHENTICATED': { | |
const retryRequest = () => { | |
operation.setContext({ | |
headers: { | |
...headers, | |
Authorization: buildAuthHeader( | |
globalProvider.authAccessToken(), | |
).Authorization, | |
}, | |
}); | |
const subscriber = { | |
next: observer.next.bind(observer), | |
error: observer.error.bind(observer), | |
complete: observer.complete.bind(observer), | |
}; | |
return forward(operation).subscribe(subscriber); | |
}; | |
const { headers } = operation.getContext(); | |
const globalProvider = await getGlobalProvider(); | |
if (!isFetchingToken) { | |
isFetchingToken = true; | |
try { | |
await globalProvider.refreshAccessTokenReq(); | |
isFetchingToken = false; | |
onTokenRefreshed(null); | |
tokenSubscribers = []; | |
return retryRequest(); | |
} catch (e) { | |
onTokenRefreshed( | |
new Error('Unable to refresh access token'), | |
); | |
tokenSubscribers = []; | |
isFetchingToken = false; | |
await globalProvider.logOut({ isForced: true }); | |
return observer.error(graphQLErrors[index]); | |
} | |
} | |
const tokenSubscriber = new Promise(resolve => { | |
subscribeTokenRefresh(errRefreshing => { | |
if (!errRefreshing) return resolve(retryRequest()); | |
}); | |
}); | |
return tokenSubscriber; | |
} | |
default: | |
return observer.next(response); | |
} | |
}); | |
} | |
if (networkError) { | |
return observer.error(networkError); | |
} | |
}), | |
); | |
export default refreshAuthTokenLink; |
I've been struggling to get my refresh token logic to work for multiple failed request for a couple days now, and this finally solved it for me. Thank you for sharing this!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@grgurev
buildAuthHeader
is just a simple function I use to build the authorization header: https://github.com/AMGAVentures/saas-boilerplate/blob/develop/packages/app/src/utils/requests.jsgetProvider
is where I keep my global application state (user authentication/profile state..etc) check out more about it here: https://github.com/AMGAVentures/saas-boilerplate/blob/develop/packages/app/src/GlobalState.js.You don't need them to make it works.. you can refactor/tweak those part as you need.