Bug: prevent concurrent access token refresh attempts
Making multiple fetch calls using OAuth2AuthCodePKCE.decorateFetchHTTPClient(fetch) while the access token is expired will result in multiple token refresh calls. Unfortunately this causes issues such as invalid_grant error responses and potentially even rate limiting. For example, gitlab.com rate limits this particular API call to 10 requests/minute.
Here's how I worked around the issue, but it would be ideal to implement this in your library instead:
export const createGitlabOAuth = () => {
let expiryPromise;
let invalidGrantPromise;
return new OAuth2AuthCodePKCE({
authorizationUrl: 'https://gitlab.com/oauth/authorize',
tokenUrl: 'https://gitlab.com/oauth/token',
clientId: process.env.REACT_APP_GITLAB_CLIENT_ID,
redirectUrl: window.location.origin,
scopes: ['api'],
extraAuthorizationParams: {
clientSecret: process.env.REACT_APP_GITLAB_SECRET,
},
onAccessTokenExpiry: async (refreshToken) => {
if (!expiryPromise) {
expiryPromise = refreshToken();
}
const result = await expiryPromise;
expiryPromise = undefined;
return result;
},
onInvalidGrant: async (refreshAuthCodeOrToken) => {
if (!invalidGrantPromise) {
invalidGrantPromise = refreshAuthCodeOrToken();
}
// This is a void promise, so don't need to return the result. Refer to the TypeScript source
// of OAuth2AuthCodePKCE. Types are great.
await invalidGrantPromise;
invalidGrantPromise = undefined;
},
});
};
Note: the onAccessTokenExpiry workaround was the important part for the specific scenario I encountered, but I think it makes sense to do in onInvalidGrant too.
For additional context, check out the linked PR in organice: https://github.com/200ok-ch/organice/pull/740
Thank you again for your explanation and suggested fix! I've just been really busy the past months. Bity development team is practically 2 people for the past year, and yeah, I'm leaving now, so this may not see any updates for awhile. I know personally if I ever need an OAuth2 solution though, I'll be using this one I've created :slightly_smiling_face: