From 15710a03a35525d5bfec2da2486b5861ee88a4e3 Mon Sep 17 00:00:00 2001 From: Joshua Herron Date: Wed, 18 Mar 2026 16:46:54 -0400 Subject: [PATCH] Ignore hash during callback URL check --- .../src/lib/utils/url/url.service.spec.ts | 140 +++++++++++++----- .../src/lib/utils/url/url.service.ts | 2 + 2 files changed, 104 insertions(+), 38 deletions(-) diff --git a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts index 10190c0ea..9e786f729 100644 --- a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts @@ -45,7 +45,8 @@ describe('UrlService Tests', () => { describe('getUrlWithoutQueryParameters', () => { it('should return a new instance of the passed URL without any query parameters', () => { - const url = new URL('https://any.url'); const params = [ + const url = new URL('https://any.url'); + const params = [ { key: 'doot', value: 'boop' }, { key: 'blep', value: 'blep' }, ]; @@ -63,7 +64,8 @@ describe('UrlService Tests', () => { }); describe('queryParametersExist', () => { - const expected = new URLSearchParams(); const params = [ + const expected = new URLSearchParams(); + const params = [ { key: 'doot', value: 'boop' }, { key: 'blep', value: 'blep' }, ]; @@ -75,7 +77,8 @@ describe('UrlService Tests', () => { const matchingUrls = [ new URL('https://any.url?doot=boop&blep=blep'), new URL('https://any.url?doot=boop&blep=blep&woop=doot'), - ]; const nonMatchingUrls = [ + ]; + const nonMatchingUrls = [ new URL('https://any.url?doot=boop'), new URL('https://any.url?blep=blep&woop=doot'), ]; @@ -145,6 +148,30 @@ describe('UrlService Tests', () => { expect(result).toBe(isCallbackFromSts); }); }); + + const hashTests = [ + { + url: 'https://any.url/?state=somevalue#code=anyvalue', + redirectUrl: 'https://any.url/?state=somevalue', + expected: true, + }, + { + url: 'https://any.url/?state=somevalue', + redirectUrl: 'https://any.url/?state=somevalue', + expected: true, + }, + ]; + + for (const { url, redirectUrl, expected } of hashTests) { + it(`should return ${expected} for url ${url}`, () => { + const result = service.isCallbackFromSts(url, { + redirectUrl, + checkRedirectUrlWhenCheckingIfIsCallback: true, + }); + + expect(result).toBe(expected); + }); + } }); describe('getUrlParameter', () => { @@ -351,7 +378,8 @@ describe('UrlService Tests', () => { 'nonce', 'state', config - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=code' + @@ -389,7 +417,8 @@ describe('UrlService Tests', () => { 'state', config, 'myprompt' - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=id_token%20token' + @@ -427,7 +456,8 @@ describe('UrlService Tests', () => { config, 'myprompt', { to: 'add', as: 'well' } - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=id_token%20token' + @@ -465,7 +495,8 @@ describe('UrlService Tests', () => { 'nonce', 'state', config - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=id_token%20token' + @@ -505,7 +536,8 @@ describe('UrlService Tests', () => { 'nonce', 'state', config - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=id_token%20token' + @@ -548,7 +580,8 @@ describe('UrlService Tests', () => { 'nonce', 'state', config - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=id_token%20token' + @@ -590,7 +623,8 @@ describe('UrlService Tests', () => { config, null, { to: 'add', as: 'well' } - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=id_token%20token' + @@ -629,7 +663,8 @@ describe('UrlService Tests', () => { config, null, { to: 'add', as: 'well' } - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=id_token%20token' + @@ -666,7 +701,8 @@ describe('UrlService Tests', () => { config, null, { to: 'add', as: 'well' } - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=id_token%20token' + @@ -702,7 +738,8 @@ describe('UrlService Tests', () => { 'nonce', 'state', config - ); const expectValue = + ); + const expectValue = 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in' + '&client_id=myid' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + @@ -738,7 +775,8 @@ describe('UrlService Tests', () => { 'nonce', 'state', config - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=id_token%20token' + @@ -775,7 +813,8 @@ describe('UrlService Tests', () => { 'state', config, 'somePrompt' - ); const expectValue = + ); + const expectValue = 'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' + '&redirect_uri=https%3A%2F%2Flocalhost%3A44386' + '&response_type=code' + @@ -901,7 +940,8 @@ describe('UrlService Tests', () => { revocationEndpoint, }); - const value = service.getRevocationEndpointUrl(config); const expectValue = 'http://example'; + const value = service.getRevocationEndpointUrl(config); + const expectValue = 'http://example'; expect(value).toEqual(expectValue); }); @@ -926,7 +966,8 @@ describe('UrlService Tests', () => { revocationEndpoint, }); - const value = service.getRevocationEndpointUrl(config); const expectValue = 'http://example'; + const value = service.getRevocationEndpointUrl(config); + const expectValue = 'http://example'; expect(value).toEqual(expectValue); }); @@ -1006,7 +1047,8 @@ describe('UrlService Tests', () => { scope: 'testScope', hdParam: undefined, customParamsAuthRequest: undefined, - } as OpenIdConfiguration; const authorizationEndpoint = 'authorizationEndpoint'; + } as OpenIdConfiguration; + const authorizationEndpoint = 'authorizationEndpoint'; spyOn(jwtWindowCryptoService, 'generateCodeChallenge').and.returnValue( of('some-code-challenge') @@ -1413,7 +1455,9 @@ describe('UrlService Tests', () => { const config = { silentRenewUrl, - }; const serviceAsAny = service as any; const result = serviceAsAny.createUrlImplicitFlowWithSilentRenew(config); + }; + const serviceAsAny = service as any; + const result = serviceAsAny.createUrlImplicitFlowWithSilentRenew(config); expect(result).toBeNull(); }); @@ -1445,7 +1489,8 @@ describe('UrlService Tests', () => { authorizationEndpoint, }); - const serviceAsAny = service as any; const result = serviceAsAny.createUrlImplicitFlowWithSilentRenew(config); + const serviceAsAny = service as any; + const result = serviceAsAny.createUrlImplicitFlowWithSilentRenew(config); expect(result).toBe( `authorizationEndpoint?client_id=${clientId}&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}&prompt=none` @@ -1474,7 +1519,8 @@ describe('UrlService Tests', () => { .withArgs('authWellKnownEndPoints', config) .and.returnValue(null); - const serviceAsAny = service as any; const result = serviceAsAny.createUrlImplicitFlowWithSilentRenew(config); + const serviceAsAny = service as any; + const result = serviceAsAny.createUrlImplicitFlowWithSilentRenew(config); expect(result).toBe(null); }); @@ -1502,7 +1548,9 @@ describe('UrlService Tests', () => { const config = { silentRenewUrl, - }; const serviceAsAny = service as any; const resultObs$ = serviceAsAny.createUrlCodeFlowWithSilentRenew(config); + }; + const serviceAsAny = service as any; + const resultObs$ = serviceAsAny.createUrlCodeFlowWithSilentRenew(config); resultObs$.subscribe((result: any) => { expect(result).toBe(''); @@ -1542,7 +1590,8 @@ describe('UrlService Tests', () => { .withArgs('authWellKnownEndPoints', config) .and.returnValue({ authorizationEndpoint }); - const serviceAsAny = service as any; const resultObs$ = serviceAsAny.createUrlCodeFlowWithSilentRenew(config); + const serviceAsAny = service as any; + const resultObs$ = serviceAsAny.createUrlCodeFlowWithSilentRenew(config); resultObs$.subscribe((result: any) => { expect(result).toBe( @@ -1580,7 +1629,8 @@ describe('UrlService Tests', () => { .withArgs('authWellKnownEndPoints', config) .and.returnValue(null); - const serviceAsAny = service as any; const resultObs$ = serviceAsAny.createUrlCodeFlowWithSilentRenew(config); + const serviceAsAny = service as any; + const resultObs$ = serviceAsAny.createUrlCodeFlowWithSilentRenew(config); resultObs$.subscribe((result: any) => { expect(result).toBe(''); @@ -1614,7 +1664,8 @@ describe('UrlService Tests', () => { .withArgs('authWellKnownEndPoints', config) .and.returnValue({ authorizationEndpoint }); - const serviceAsAny = service as any; const result = serviceAsAny.createUrlImplicitFlowAuthorize(config); + const serviceAsAny = service as any; + const result = serviceAsAny.createUrlImplicitFlowAuthorize(config); expect(result).toBe( `authorizationEndpoint?client_id=clientId&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}` @@ -1639,7 +1690,8 @@ describe('UrlService Tests', () => { .withArgs('authWellKnownEndPoints', config) .and.returnValue(null); - const serviceAsAny = service as any; const result = serviceAsAny.createUrlImplicitFlowAuthorize(config); + const serviceAsAny = service as any; + const result = serviceAsAny.createUrlImplicitFlowAuthorize(config); expect(result).toBe(null); }); @@ -1661,7 +1713,8 @@ describe('UrlService Tests', () => { .withArgs('authWellKnownEndPoints', config) .and.returnValue(null); - const serviceAsAny = service as any; const result = serviceAsAny.createUrlImplicitFlowAuthorize(config); + const serviceAsAny = service as any; + const result = serviceAsAny.createUrlImplicitFlowAuthorize(config); expect(result).toBe(null); }); @@ -1682,7 +1735,8 @@ describe('UrlService Tests', () => { ).and.returnValue(state); spyOn(flowsDataService, 'createNonce').and.returnValue(nonce); - const serviceAsAny = service as any; const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config); + const serviceAsAny = service as any; + const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config); resultObs$.subscribe((result: any) => { expect(result).toBeNull(); @@ -1721,7 +1775,8 @@ describe('UrlService Tests', () => { .withArgs('authWellKnownEndPoints', config) .and.returnValue({ authorizationEndpoint }); - const serviceAsAny = service as any; const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config); + const serviceAsAny = service as any; + const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config); resultObs$.subscribe((result: any) => { expect(result).toBe( @@ -1765,7 +1820,8 @@ describe('UrlService Tests', () => { .withArgs('authWellKnownEndPoints', config) .and.returnValue({ authorizationEndpoint }); - const serviceAsAny = service as any; const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config, { + const serviceAsAny = service as any; + const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config, { customParams: { to: 'add', as: 'well' }, }); @@ -1802,7 +1858,8 @@ describe('UrlService Tests', () => { .withArgs('authWellKnownEndPoints', config) .and.returnValue(null); - const serviceAsAny = service as any; const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config); + const serviceAsAny = service as any; + const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config); resultObs$.subscribe((result: any) => { expect(result).toBe(''); @@ -1831,7 +1888,8 @@ describe('UrlService Tests', () => { }); // Act - const value = service.getEndSessionUrl(config); // Assert + const value = service.getEndSessionUrl(config); + // Assert const expectValue = 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; @@ -1852,7 +1910,8 @@ describe('UrlService Tests', () => { }); // Act - const value = service.getEndSessionUrl(config); // Assert + const value = service.getEndSessionUrl(config); + // Assert const expectValue = 'http://example?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; @@ -1873,7 +1932,8 @@ describe('UrlService Tests', () => { }); // Act - const value = service.getEndSessionUrl(config, { param: 'to-add' }); // Assert + const value = service.getEndSessionUrl(config, { param: 'to-add' }); + // Assert const expectValue = 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized¶m=to-add'; @@ -1898,7 +1958,8 @@ describe('UrlService Tests', () => { ); // Act - const value = service.getEndSessionUrl(config); // Assert + const value = service.getEndSessionUrl(config); + // Assert const expectValue = 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in' + '&id_token_hint=UzI1NiIsImtpZCI6Il' + @@ -1920,7 +1981,8 @@ describe('UrlService Tests', () => { spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); // Act - const value = service.getEndSessionUrl(config); // Assert + const value = service.getEndSessionUrl(config); + // Assert const expectValue = 'http://example?id_token_hint=mytoken'; expect(value).toEqual(expectValue); @@ -1948,8 +2010,10 @@ describe('UrlService Tests', () => { authority: 'something.auth0.com', clientId: 'someClientId', postLogoutRedirectUri: 'https://localhost:1234/unauthorized', - }; // Act - const value = service.getEndSessionUrl(config); // Assert + }; + // Act + const value = service.getEndSessionUrl(config); + // Assert const expectValue = `something.auth0.com/v2/logout?client_id=someClientId&returnTo=https://localhost:1234/unauthorized`; expect(value).toEqual(expectValue); diff --git a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts index cc7c787fe..72a8e6d6d 100644 --- a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts @@ -52,6 +52,8 @@ export class UrlService { u.searchParams.delete(key); }); + u.hash = ''; + return u; }