diff --git a/change/@azure-msal-browser-08c573f5-354f-4cc7-829c-aaab16e17860.json b/change/@azure-msal-browser-08c573f5-354f-4cc7-829c-aaab16e17860.json new file mode 100644 index 0000000000..f13efa5c4e --- /dev/null +++ b/change/@azure-msal-browser-08c573f5-354f-4cc7-829c-aaab16e17860.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Fix token refreshes with relative redirectUri #6761", + "packageName": "@azure/msal-browser", + "email": "thomas.norling@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-msal-common-bdfc362d-71b8-4f7a-81f9-e2bff4f9f887.json b/change/@azure-msal-common-bdfc362d-71b8-4f7a-81f9-e2bff4f9f887.json new file mode 100644 index 0000000000..2fce1b0163 --- /dev/null +++ b/change/@azure-msal-common-bdfc362d-71b8-4f7a-81f9-e2bff4f9f887.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Fix token refreshes with relative redirectUri #6761", + "packageName": "@azure/msal-common", + "email": "thomas.norling@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-browser/src/interaction_client/SilentRefreshClient.ts b/lib/msal-browser/src/interaction_client/SilentRefreshClient.ts index dba9f11600..5e094ee0eb 100644 --- a/lib/msal-browser/src/interaction_client/SilentRefreshClient.ts +++ b/lib/msal-browser/src/interaction_client/SilentRefreshClient.ts @@ -44,6 +44,14 @@ export class SilentRefreshClient extends StandardInteractionClient { ...request, ...baseRequest, }; + + if (request.redirectUri) { + // Make sure any passed redirectUri is converted to an absolute URL - redirectUri is not a required parameter for refresh token redemption so only include if explicitly provided + silentRequest.redirectUri = this.getRedirectUri( + request.redirectUri + ); + } + const serverTelemetryManager = this.initializeServerTelemetryManager( ApiId.acquireTokenSilent_silentFlow ); diff --git a/lib/msal-browser/test/interaction_client/SilentRefreshClient.spec.ts b/lib/msal-browser/test/interaction_client/SilentRefreshClient.spec.ts index 8acf8966fb..a1d32cfe7a 100644 --- a/lib/msal-browser/test/interaction_client/SilentRefreshClient.spec.ts +++ b/lib/msal-browser/test/interaction_client/SilentRefreshClient.spec.ts @@ -154,6 +154,62 @@ describe("SilentRefreshClient", () => { expect(tokenResp).toEqual(testTokenResponse); }); + it("Relative redirectUri is converted to absolute", async () => { + const testServerTokenResponse = { + token_type: TEST_CONFIG.TOKEN_TYPE_BEARER, + scope: TEST_CONFIG.DEFAULT_SCOPES.join(" "), + expires_in: TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN, + ext_expires_in: TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN, + access_token: TEST_TOKENS.ACCESS_TOKEN, + refresh_token: TEST_TOKENS.REFRESH_TOKEN, + id_token: TEST_TOKENS.IDTOKEN_V2, + }; + const testTokenResponse: AuthenticationResult = { + authority: TEST_CONFIG.validAuthority, + uniqueId: testIdTokenClaims.oid || "", + tenantId: testIdTokenClaims.tid || "", + scopes: ["scope1"], + idToken: testServerTokenResponse.id_token, + idTokenClaims: testIdTokenClaims, + accessToken: testServerTokenResponse.access_token, + fromCache: false, + correlationId: RANDOM_TEST_GUID, + expiresOn: new Date( + Date.now() + testServerTokenResponse.expires_in * 1000 + ), + account: testAccount, + tokenType: AuthenticationScheme.BEARER, + }; + const silentATStub = jest + .spyOn( + RefreshTokenClient.prototype, + "acquireTokenByRefreshToken" + ) + .mockResolvedValue(testTokenResponse); + const tokenRequest: CommonSilentFlowRequest = { + scopes: ["scope1"], + account: testAccount, + authority: TEST_CONFIG.validAuthority, + authenticationScheme: AuthenticationScheme.BEARER, + correlationId: TEST_CONFIG.CORRELATION_ID, + forceRefresh: false, + redirectUri: "/", // relative redirectUri + }; + const expectedTokenRequest: CommonSilentFlowRequest = { + ...tokenRequest, + scopes: ["scope1"], + authority: `${Constants.DEFAULT_AUTHORITY}`, + correlationId: RANDOM_TEST_GUID, + forceRefresh: false, + redirectUri: "https://localhost:8081/", // absolute redirectUri + }; + const tokenResp = await silentRefreshClient.acquireToken( + tokenRequest + ); + expect(silentATStub).toHaveBeenCalledWith(expectedTokenRequest); + expect(tokenResp).toEqual(testTokenResponse); + }); + describe("storeInCache tests", () => { beforeEach(() => { const rtEntity = { diff --git a/lib/msal-common/src/request/CommonSilentFlowRequest.ts b/lib/msal-common/src/request/CommonSilentFlowRequest.ts index 9de85d3a2d..43e51b0ab1 100644 --- a/lib/msal-common/src/request/CommonSilentFlowRequest.ts +++ b/lib/msal-common/src/request/CommonSilentFlowRequest.ts @@ -24,6 +24,8 @@ export type CommonSilentFlowRequest = BaseAuthRequest & { account: AccountInfo; /** Skip cache lookup and forces network call(s) to get fresh tokens */ forceRefresh: boolean; + /** RedirectUri registered on the app registration - only required in brokering scenarios */ + redirectUri?: string; /** Key value pairs to include on the POST body to the /token endpoint */ tokenBodyParameters?: StringDict; /** If refresh token will expire within the configured value, consider it already expired. Used to pre-emptively invoke interaction when cached refresh token is close to expiry. */