diff --git a/change/@azure-msal-node-2c770c1f-88f0-42d9-b87e-591e60e975b6.json b/change/@azure-msal-node-2c770c1f-88f0-42d9-b87e-591e60e975b6.json new file mode 100644 index 0000000000..3257a2d43d --- /dev/null +++ b/change/@azure-msal-node-2c770c1f-88f0-42d9-b87e-591e60e975b6.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "ClientCredential and OBO acquireToken requests with claims will now skip the cache", + "packageName": "@azure/msal-node", + "email": "rginsburg@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-node/src/client/ClientCredentialClient.ts b/lib/msal-node/src/client/ClientCredentialClient.ts index bd750e5abc..6fe7c2c0e7 100644 --- a/lib/msal-node/src/client/ClientCredentialClient.ts +++ b/lib/msal-node/src/client/ClientCredentialClient.ts @@ -59,7 +59,7 @@ export class ClientCredentialClient extends BaseClient { public async acquireToken( request: CommonClientCredentialRequest ): Promise { - if (request.skipCache) { + if (request.skipCache || request.claims) { return this.executeTokenRequest(request, this.authority); } diff --git a/lib/msal-node/src/client/OnBehalfOfClient.ts b/lib/msal-node/src/client/OnBehalfOfClient.ts index 3fcc427955..af0705df6d 100644 --- a/lib/msal-node/src/client/OnBehalfOfClient.ts +++ b/lib/msal-node/src/client/OnBehalfOfClient.ts @@ -58,7 +58,7 @@ export class OnBehalfOfClient extends BaseClient { request.oboAssertion ); - if (request.skipCache) { + if (request.skipCache || request.claims) { return this.executeTokenRequest( request, this.authority, diff --git a/lib/msal-node/src/config/Configuration.ts b/lib/msal-node/src/config/Configuration.ts index 9784325450..ba57eb4118 100644 --- a/lib/msal-node/src/config/Configuration.ts +++ b/lib/msal-node/src/config/Configuration.ts @@ -65,6 +65,9 @@ export type NodeAuthOptions = { */ export type CacheOptions = { cachePlugin?: ICachePlugin; + /** + * @deprecated claims-based-caching functionality will be removed in the next version of MSALJS + */ claimsBasedCachingEnabled?: boolean; }; diff --git a/lib/msal-node/test/client/ClientCredentialClient.spec.ts b/lib/msal-node/test/client/ClientCredentialClient.spec.ts index 6efc65f76c..6bbdc80a7a 100644 --- a/lib/msal-node/test/client/ClientCredentialClient.spec.ts +++ b/lib/msal-node/test/client/ClientCredentialClient.spec.ts @@ -420,14 +420,17 @@ describe("ClientCredentialClient unit tests", () => { ])( "Validates that claims and client capabilities are correctly merged", async (claims, mergedClaims) => { - clientCredentialRequest.claims = claims; + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the IDP const authResult = (await client.acquireToken( clientCredentialRequest )) as AuthenticationResult; expect(authResult.accessToken).toEqual( CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token ); + expect(authResult.fromCache).toBe(false); + // verify that the client capabilities have been merged with the (empty) claims const returnVal: string = createTokenRequestBodySpy.mock .results[0].value as string; expect( @@ -437,8 +440,10 @@ describe("ClientCredentialClient unit tests", () => { .filter((key: string) => key.includes("claims="))[0] .split("claims=")[1] ) - ).toEqual(mergedClaims); + ).toEqual(CAE_CONSTANTS.MERGED_EMPTY_CLAIMS); + // acquire a token (without changing anything) and verify that it comes from the cache + // verify that it comes from the cache const cachedAuthResult = (await client.acquireToken( clientCredentialRequest )) as AuthenticationResult; @@ -446,6 +451,51 @@ describe("ClientCredentialClient unit tests", () => { CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token ); expect(cachedAuthResult.fromCache).toBe(true); + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + clientCredentialRequest.claims = claims; + const authResult2 = (await client.acquireToken( + clientCredentialRequest + )) as AuthenticationResult; + expect(authResult2.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult2.fromCache).toBe(false); + + // verify that the client capabilities have been merged with the claims + const returnVal2: string = createTokenRequestBodySpy.mock + .results[1].value as string; + expect( + decodeURIComponent( + returnVal2 + .split("&") + .filter((key: string) => key.includes("claims="))[0] + .split("claims=")[1] + ) + ).toEqual(mergedClaims); + + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the cache + delete clientCredentialRequest.claims; + const authResult3 = (await client.acquireToken( + clientCredentialRequest + )) as AuthenticationResult; + expect(authResult3.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult3.fromCache).toBe(true); + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + clientCredentialRequest.claims = claims; + const authResult4 = (await client.acquireToken( + clientCredentialRequest + )) as AuthenticationResult; + expect(authResult4.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult4.fromCache).toBe(false); } ); }); diff --git a/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts b/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts index 583f0ce76d..41a2269a5a 100644 --- a/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts +++ b/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts @@ -132,7 +132,8 @@ describe("ConfidentialClientApplication", () => { ])( "Validates that claims and client capabilities are correctly merged", async (claims, mergedClaims) => { - authorizationCodeRequest.claims = claims; + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the IDP const authResult = (await client.acquireTokenByCode( authorizationCodeRequest )) as AuthenticationResult; @@ -140,7 +141,9 @@ describe("ConfidentialClientApplication", () => { CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body .access_token ); + expect(authResult.fromCache).toBe(false); + // verify that the client capabilities have been merged with the (empty) claims const returnVal: string = (await createTokenRequestBodySpy .mock.results[0].value) as string; expect( @@ -152,9 +155,35 @@ describe("ConfidentialClientApplication", () => { )[0] .split("claims=")[1] ) - ).toEqual(mergedClaims); + ).toEqual(CAE_CONSTANTS.MERGED_EMPTY_CLAIMS); // skip cache lookup verification because acquireTokenByCode does not pull elements from the cache + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + authorizationCodeRequest.claims = claims; + const authResult2 = (await client.acquireTokenByCode( + authorizationCodeRequest + )) as AuthenticationResult; + expect(authResult2.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body + .access_token + ); + expect(authResult2.fromCache).toBe(false); + + // verify that the client capabilities have been merged with the claims + const returnVal2: string = (await createTokenRequestBodySpy + .mock.results[1].value) as string; + expect( + decodeURIComponent( + returnVal2 + .split("&") + .filter((key: string) => + key.includes("claims=") + )[0] + .split("claims=")[1] + ) + ).toEqual(mergedClaims); } ); }); diff --git a/lib/msal-node/test/client/OnBehalfOfClient.spec.ts b/lib/msal-node/test/client/OnBehalfOfClient.spec.ts index 3ec594205c..ee670aecd9 100644 --- a/lib/msal-node/test/client/OnBehalfOfClient.spec.ts +++ b/lib/msal-node/test/client/OnBehalfOfClient.spec.ts @@ -142,14 +142,17 @@ describe("OnBehalfOf unit tests", () => { ])( "Validates that claims and client capabilities are correctly merged", async (claims, mergedClaims) => { - oboRequest.claims = claims; + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the IDP const authResult = (await client.acquireToken( oboRequest )) as AuthenticationResult; expect(authResult.accessToken).toEqual( AUTHENTICATION_RESULT.body.access_token ); + expect(authResult.fromCache).toBe(false); + // verify that the client capabilities have been merged with the (empty) claims const returnVal: string = createTokenRequestBodySpy.mock .results[0].value as string; expect( @@ -161,8 +164,10 @@ describe("OnBehalfOf unit tests", () => { )[0] .split("claims=")[1] ) - ).toEqual(mergedClaims); + ).toEqual(CAE_CONSTANTS.MERGED_EMPTY_CLAIMS); + // acquire a token (without changing anything) and verify that it comes from the cache + // verify that it comes from the cache const cachedAuthResult = (await client.acquireToken( oboRequest )) as AuthenticationResult; @@ -170,6 +175,53 @@ describe("OnBehalfOf unit tests", () => { AUTHENTICATION_RESULT.body.access_token ); expect(cachedAuthResult.fromCache).toBe(true); + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + oboRequest.claims = claims; + const authResult2 = (await client.acquireToken( + oboRequest + )) as AuthenticationResult; + expect(authResult2.accessToken).toEqual( + AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult2.fromCache).toBe(false); + + // verify that the client capabilities have been merged with the claims + const returnVal2: string = createTokenRequestBodySpy.mock + .results[1].value as string; + expect( + decodeURIComponent( + returnVal2 + .split("&") + .filter((key: string) => + key.includes("claims=") + )[0] + .split("claims=")[1] + ) + ).toEqual(mergedClaims); + + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the cache + delete oboRequest.claims; + const authResult3 = (await client.acquireToken( + oboRequest + )) as AuthenticationResult; + expect(authResult3.accessToken).toEqual( + AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult3.fromCache).toBe(true); + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + oboRequest.claims = claims; + const authResult4 = (await client.acquireToken( + oboRequest + )) as AuthenticationResult; + expect(authResult4.accessToken).toEqual( + AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult4.fromCache).toBe(false); } ); });