diff --git a/src/authentication/authenticationContext.spec.ts b/src/authentication/authenticationContext.spec.ts index ef9876b..365ed15 100644 --- a/src/authentication/authenticationContext.spec.ts +++ b/src/authentication/authenticationContext.spec.ts @@ -103,12 +103,10 @@ describe('AuthenticationContext', function() { expect(await authenticationContext.isSignedIn()).toBe(false); }); - it('completes sign-out even when getIdToken throws due to expired token with no refresh source', async () => { + it('completes sign-out when user is not signed in (getIdToken returns null for anonymous user)', async () => { (authenticationContext as any).accessTokenResponse = { isValid: () => false }; - const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); await authenticationContext.signOut(); - warnSpy.mockRestore(); expect(await authenticationContext.isSignedIn()).toBe(false); }); @@ -304,6 +302,15 @@ describe('AuthenticationContext', function() { expect(accessToken).toBe(fakeAccessToken); }); + it('returns null when access token is expired and no refresh source is available (anonymous user)', async () => { + (authenticationContext as any).accessTokenResponse = { isValid: () => false }; + // No refresh token in localStorage, no refreshAccessToken callback + + const token = await authenticationContext.getAccessToken(); + + expect(token).toBeNull(); + }); + it('refreshes the accessToken if it is no longer valid', async () => { (authenticationContext as any).accessTokenResponse = { isValid: () => false }; localStorage.setItem('elfsquad_refresh_token', 'STORED_TOKEN'); @@ -615,6 +622,15 @@ describe('AuthenticationContext', function() { describe('getIdToken', function() { + it('returns null when access token is expired and no refresh source is available (anonymous user)', async () => { + (authenticationContext as any).accessTokenResponse = { isValid: () => false }; + // No refresh token in localStorage, no refreshAccessToken callback + + const idToken = await authenticationContext.getIdToken(); + + expect(idToken).toBeNull(); + }); + it('returns the id token after refreshing when the access token is expired', async () => { const refreshMock = jest.fn().mockResolvedValue({ accessToken: 'NEW_TOKEN', expiresIn: 3600, idToken: 'NEW_ID_TOKEN' }); authenticationContext = new AuthenticationContext({ diff --git a/src/authentication/authenticationContext.ts b/src/authentication/authenticationContext.ts index 3ef1d70..2655c2f 100644 --- a/src/authentication/authenticationContext.ts +++ b/src/authentication/authenticationContext.ts @@ -249,7 +249,7 @@ export class AuthenticationContext { } if (!this.options.refreshAccessToken && !TokenStore.hasRefreshToken()) { - throw new Error('@elfsquad/authentication: Access token expired and no refresh source is available. Ensure offline_access is in the requested scope or provide a refreshAccessToken callback.'); + return null; } if (this._refreshTokenPromise) { @@ -291,7 +291,10 @@ export class AuthenticationContext { // Delegate to getAccessToken() so the shared _refreshTokenPromise gate is used, // preventing duplicate refresh calls when getAccessToken() and getIdToken() are // awaited concurrently. - await this.getAccessToken(); + const accessToken = await this.getAccessToken(); + if (!accessToken) { + return null; + } return this.accessTokenResponse.idToken; }