Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,41 @@ describe('OIDC plugin (mock OIDC provider)', function () {
additionalIssuerMetadata = undefined;
});

context('with a broken token refresh flow', function () {
let plugin: MongoDBOIDCPlugin;

beforeEach(function () {
plugin = createMongoDBOIDCPlugin({
openBrowserTimeout: 60_000,
openBrowser: fetchBrowser,
allowedFlows: ['auth-code'],
redirectURI: 'http://localhost:0/callback',
});

overrideRequestHandler = (url, req, res) => {
if (new URL(url).searchParams.get('grant_type') === 'refresh_token') {
res.writeHead(500);
res.end('Internal Server Error');
}
};
});

it('will keep using an unexpired token if the refresh endpoint is broken even if refresh is overdue', async function () {
getTokenPayload = () => {
return { ...tokenPayload, expires_in: 60 /* one minute */ };
};
const req = async () =>
await requestToken(plugin, {
issuer: provider.issuer,
clientId: 'mockclientid',
requestScopes: [],
});
const result1 = await req();
const result2 = await req();
expect(result1).to.deep.equal(result2);
});
});

context('with different supported built-in scopes', function () {
let getScopes: () => Promise<string[]>;

Expand Down
35 changes: 27 additions & 8 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -964,27 +964,46 @@ export class MongoDBOIDCPluginImpl implements MongoDBOIDCPlugin {

try {
get_tokens: {
if (
!forceRefreshOrReauth &&
tokenExpiryInSeconds(
state.currentTokenSet?.set,
passIdTokenAsAccessToken
) >
5 * 60
) {
const currentSetExpiresInSeconds = tokenExpiryInSeconds(
state.currentTokenSet?.set,
passIdTokenAsAccessToken
);
// If the current token set has a decent amount of validity, just keep using it.
if (!forceRefreshOrReauth && currentSetExpiresInSeconds > 5 * 60) {
this.logger.emit('mongodb-oidc-plugin:skip-auth-attempt', {
authStateId: state.id,
reason: 'not-expired',
});
break get_tokens;
}
// If the current token set is close to expiry, try to acquire a fresh token
// if possible.
if (await state.currentTokenSet?.tryRefresh?.()) {
this.logger.emit('mongodb-oidc-plugin:skip-auth-attempt', {
authStateId: state.id,
reason: 'refresh-succeeded',
});
break get_tokens;
}
// If the current token set is close to expiry, and refreshing failed, that can
// just mean that the token refresh mechanism are not available.
// We want to avoid a situation in which two initiateAuthAttempt() calls are made
// right after each other, the token returned from the first one has a short validity
// (i.e. up to or less than 5 minutes), and the second one then ignores the token
// from the first one, effectively keeping the user in a loop of 'overly' eager
// re-authentication interactions.
// This means we effectively have a minimum requirement of tokens being valid
// for at least 10 seconds after being issued OR a working token refresh flow, which
// seems like a reasonable expectation.
if (!forceRefreshOrReauth && currentSetExpiresInSeconds > 10) {
this.logger.emit('mongodb-oidc-plugin:skip-auth-attempt', {
authStateId: state.id,
reason: 'not-expired-refresh-failed',
});
break get_tokens;
}
// If no automatic mechanism for acquiring a token is available, we need to start
// an authentication flow that (typically) involves user interaction.
state.currentTokenSet = null;
let error;
const currentAllowedFlowSet = await this.getAllowedFlows({ signal });
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export interface MongoDBOIDCLogEventsMap {
}) => void;
'mongodb-oidc-plugin:skip-auth-attempt': (event: {
authStateId: string;
reason: string;
reason: 'not-expired' | 'not-expired-refresh-failed' | 'refresh-succeeded';
}) => void;
'mongodb-oidc-plugin:auth-failed': (event: {
authStateId: string;
Expand Down
Loading