Skip to content

Commit c0abbd6

Browse files
[ID-3716] feat: revoke refresh token on logout (#2647)
1 parent 4ae8530 commit c0abbd6

File tree

2 files changed

+76
-10
lines changed

2 files changed

+76
-10
lines changed

packages/passport/sdk/src/authManager.test.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ describe('AuthManager', () => {
9494
let mockStoreUser: jest.Mock;
9595
let mockOverlayAppend: jest.Mock;
9696
let mockOverlayRemove: jest.Mock;
97+
let mockRevokeTokens: jest.Mock;
9798

9899
beforeEach(() => {
99100
mockSigninPopup = jest.fn();
@@ -106,6 +107,7 @@ describe('AuthManager', () => {
106107
mockStoreUser = jest.fn();
107108
mockOverlayAppend = jest.fn();
108109
mockOverlayRemove = jest.fn();
110+
mockRevokeTokens = jest.fn();
109111
(UserManager as jest.Mock).mockReturnValue({
110112
signinPopup: mockSigninPopup,
111113
signinCallback: mockSigninCallback,
@@ -115,6 +117,7 @@ describe('AuthManager', () => {
115117
getUser: mockGetUser,
116118
signinSilent: mockSigninSilent,
117119
storeUser: mockStoreUser,
120+
revokeTokens: mockRevokeTokens,
118121
});
119122
(Overlay as jest.Mock).mockReturnValue({
120123
append: mockOverlayAppend,
@@ -140,11 +143,13 @@ describe('AuthManager', () => {
140143
userinfo_endpoint: `${config.authenticationDomain}/userinfo`,
141144
end_session_endpoint: `${config.authenticationDomain}${logoutEndpoint}`
142145
+ `?client_id=${config.oidcConfiguration.clientId}`,
146+
revocation_endpoint: `${config.authenticationDomain}/oauth/revoke`,
143147
},
144148
popup_redirect_uri: config.oidcConfiguration.popupRedirectUri,
145149
redirect_uri: config.oidcConfiguration.redirectUri,
146150
scope: config.oidcConfiguration.scope,
147151
userStore: expect.any(WebStorageStateStore),
152+
revokeTokenTypes: ['refresh_token'],
148153
});
149154
});
150155

@@ -159,6 +164,7 @@ describe('AuthManager', () => {
159164
extraQueryParams: {
160165
audience: configWithAudience.oidcConfiguration.audience,
161166
},
167+
revokeTokenTypes: ['refresh_token'],
162168
}));
163169
});
164170
});
@@ -391,6 +397,7 @@ describe('AuthManager', () => {
391397

392398
await manager.logout();
393399

400+
expect(mockRevokeTokens).toHaveBeenCalledWith(['refresh_token']);
394401
expect(mockSignoutRedirect).toBeCalled();
395402
});
396403

@@ -402,6 +409,7 @@ describe('AuthManager', () => {
402409

403410
await manager.logout();
404411

412+
expect(mockRevokeTokens).toHaveBeenCalledWith(['refresh_token']);
405413
expect(mockSignoutRedirect).toBeCalled();
406414
});
407415

@@ -413,10 +421,46 @@ describe('AuthManager', () => {
413421

414422
await manager.logout();
415423

424+
expect(mockRevokeTokens).toHaveBeenCalledWith(['refresh_token']);
416425
expect(mockSignoutSilent).toBeCalled();
417426
});
418427

419-
it('should throw an error if user is failed to logout', async () => {
428+
describe('when revoking of refresh tokens fails', () => {
429+
it('should throw an error for redirect logout', async () => {
430+
const configuration = getConfig({
431+
logoutMode: 'redirect',
432+
});
433+
const manager = new AuthManager(configuration);
434+
mockRevokeTokens.mockRejectedValue(new Error(mockErrorMsg));
435+
436+
await expect(() => manager.logout()).rejects.toThrow(
437+
new PassportError(
438+
mockErrorMsg,
439+
PassportErrorType.LOGOUT_ERROR,
440+
),
441+
);
442+
expect(mockSignoutRedirect).not.toHaveBeenCalled();
443+
});
444+
445+
it('should throw an error for silent logout', async () => {
446+
const configuration = getConfig({
447+
logoutMode: 'silent',
448+
});
449+
const manager = new AuthManager(configuration);
450+
mockRevokeTokens.mockRejectedValue(new Error(mockErrorMsg));
451+
452+
await expect(() => manager.logout()).rejects.toThrow(
453+
new PassportError(
454+
mockErrorMsg,
455+
PassportErrorType.LOGOUT_ERROR,
456+
),
457+
);
458+
// In silent mode, signoutSilent is called in parallel with revokeTokens
459+
expect(mockSignoutSilent).toHaveBeenCalled();
460+
});
461+
});
462+
463+
it('should throw an error if user is failed to logout with redirect', async () => {
420464
const configuration = getConfig({
421465
logoutMode: 'redirect',
422466
});
@@ -430,6 +474,24 @@ describe('AuthManager', () => {
430474
PassportErrorType.LOGOUT_ERROR,
431475
),
432476
);
477+
expect(mockRevokeTokens).toHaveBeenCalledWith(['refresh_token']);
478+
});
479+
480+
it('should throw an error if user is failed to logout silently', async () => {
481+
const configuration = getConfig({
482+
logoutMode: 'silent',
483+
});
484+
const manager = new AuthManager(configuration);
485+
486+
mockSignoutSilent.mockRejectedValue(new Error(mockErrorMsg));
487+
488+
await expect(() => manager.logout()).rejects.toThrow(
489+
new PassportError(
490+
mockErrorMsg,
491+
PassportErrorType.LOGOUT_ERROR,
492+
),
493+
);
494+
expect(mockRevokeTokens).toHaveBeenCalledWith(['refresh_token']);
433495
});
434496
});
435497

packages/passport/sdk/src/authManager.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@ const getAuthConfiguration = (config: PassportConfiguration): UserManagerSetting
7171
token_endpoint: `${authenticationDomain}/oauth/token`,
7272
userinfo_endpoint: `${authenticationDomain}/userinfo`,
7373
end_session_endpoint: endSessionEndpoint.toString(),
74+
revocation_endpoint: `${authenticationDomain}/oauth/revoke`,
7475
},
7576
mergeClaims: true,
7677
automaticSilentRenew: false, // Disabled until https://github.com/authts/oidc-client-ts/issues/430 has been resolved
7778
scope: oidcConfiguration.scope,
7879
userStore,
80+
revokeTokenTypes: ['refresh_token'],
7981
extraQueryParams: {
8082
...config.extraQueryParams,
8183
...(oidcConfiguration.audience ? { audience: oidcConfiguration.audience } : {}),
@@ -436,15 +438,17 @@ export default class AuthManager {
436438
}
437439

438440
public async logout(): Promise<void> {
439-
return withPassportError<void>(
440-
async () => {
441-
if (this.logoutMode === 'silent') {
442-
return this.userManager.signoutSilent();
443-
}
444-
return this.userManager.signoutRedirect();
445-
},
446-
PassportErrorType.LOGOUT_ERROR,
447-
);
441+
return withPassportError<void>(async () => {
442+
if (this.logoutMode === 'silent') {
443+
await Promise.all([
444+
this.userManager.revokeTokens(['refresh_token']),
445+
this.userManager.signoutSilent(),
446+
]);
447+
} else {
448+
await this.userManager.revokeTokens(['refresh_token']);
449+
await this.userManager.signoutRedirect();
450+
}
451+
}, PassportErrorType.LOGOUT_ERROR);
448452
}
449453

450454
public async logoutSilentCallback(url: string): Promise<void> {

0 commit comments

Comments
 (0)