diff --git a/src/oidc/error.ts b/src/oidc/error.ts index edf3e05..66acda8 100644 --- a/src/oidc/error.ts +++ b/src/oidc/error.ts @@ -5,6 +5,7 @@ export enum OIDCErrorType { LegacyTokenRequestFailed = 'LegacyTokenRequestFailed', UserManagerCreationFailed = 'UserManagerCreationFailed', OneTimeCodeMissing = 'OneTimeCodeMissing', + FailedToRemoveSession = 'FailedToRemoveSession', } export class OIDCError extends Error { diff --git a/src/oidc/oidc.ts b/src/oidc/oidc.ts index 6a89899..7504ec8 100644 --- a/src/oidc/oidc.ts +++ b/src/oidc/oidc.ts @@ -41,6 +41,17 @@ type CreateUserManagerOptions = { postLogoutRedirectUri?: string; }; +type OAuth2LogoutOptions = { + WSLogoutAndRedirect: () => void; + redirectCallbackUri: string; + postLogoutRedirectUri: string; +}; + +type ClearOIDCStorageOptions = { + redirectCallbackUri: string; + postLogoutRedirectUri: string; +}; + /** * Fetches the OIDC configuration for the given serverUrl. * @returns {Promise} - A promise resolving to the OIDC configuration. @@ -268,7 +279,7 @@ export const createUserManager = async (options: CreateUserManagerOptions) => { * Logs out the user from the auth server and calls the callback function when the logout is complete. * @param WSLogoutAndRedirect - The callback function to call after the logout is complete */ -export const OAuth2Logout = (WSLogoutAndRedirect: () => void) => { +export const OAuth2Logout = (options: OAuth2LogoutOptions) => { const oidcEndpoints = localStorage.getItem('config.oidc_endpoints') || '{}'; const logoutUrl = getOAuthLogoutUrl() || JSON.parse(oidcEndpoints).end_session_endpoint; @@ -276,6 +287,11 @@ export const OAuth2Logout = (WSLogoutAndRedirect: () => void) => { const cleanup = () => { const iframe = document.getElementById('logout-iframe') as HTMLIFrameElement; if (iframe) iframe.remove(); + // NOTE: this will resolve issues where once you are logged out, OIDC may reuse the previous user's OIDC ID token / session data + clearOIDCStorage({ + redirectCallbackUri: options.redirectCallbackUri, + postLogoutRedirectUri: options.postLogoutRedirectUri, + }); }; const onMessage = (event: MessageEvent) => { if (event.data === 'logout_complete') { @@ -289,7 +305,7 @@ export const OAuth2Logout = (WSLogoutAndRedirect: () => void) => { secure: true, }); } - WSLogoutAndRedirect(); + options.WSLogoutAndRedirect(); cleanup(); window.removeEventListener('message', onMessage); } @@ -351,6 +367,24 @@ export const oidcLogout = async (options: RequestOidcAuthenticationOptions): Pro } }; +/** + * Clears any OIDC-related storage (localStorage, sessionStorage, etc) about the current user. + * There's 2 types of storage `oidc-client-ts` uses: state store, and user store + * User store is used for storing the access token and ID token + * Use this function when your application has logged out successfully but the session data is still there, otherwise use `OAuth2Logout` function which already handles this scenario + * + */ +export const clearOIDCStorage = async (options: ClearOIDCStorageOptions) => { + try { + const userManager = await createUserManager(options); + + await userManager.removeUser(); + } catch (error) { + if (error instanceof Error) throw new OIDCError(OIDCErrorType.FailedToRemoveSession, error.message); + throw new OIDCError(OIDCErrorType.FailedToRemoveSession, 'unable to remove OIDC session'); + } +}; + /** * Checks if the user has completed the logout flow and calls the callback function from the consumer. * At this point the user is already logged out from the auth server. This function is just to clear the FE session.