diff --git a/change/@azure-msal-browser-993190c0-4218-44e8-bb72-1cca34039657.json b/change/@azure-msal-browser-993190c0-4218-44e8-bb72-1cca34039657.json new file mode 100644 index 0000000000..d4f089a43b --- /dev/null +++ b/change/@azure-msal-browser-993190c0-4218-44e8-bb72-1cca34039657.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Add test for JS Runtime NAA scenario (#7477)", + "packageName": "@azure/msal-browser", + "email": "dasau@microsoft.com", + "dependentChangeType": "none" +} diff --git a/lib/msal-browser/test/naa/BridgeProxyConstants.ts b/lib/msal-browser/test/naa/BridgeProxyConstants.ts index d1b05aeeba..b8b60dce26 100644 --- a/lib/msal-browser/test/naa/BridgeProxyConstants.ts +++ b/lib/msal-browser/test/naa/BridgeProxyConstants.ts @@ -23,9 +23,10 @@ export const INIT_CONTEXT_RESPONSE: InitContext = { sdkVersion: "1.0.0", capabilities: { queryAccount: false }, accountContext: { - homeAccountId: "homeAccountId", + homeAccountId: + "00000000-0000-0000-66f3-3332eca7ea81.3338040d-6c67-4c5b-b112-36a304b66da", environment: "login.microsoftonline.com", - tenantId: "tenantId", + tenantId: "3338040d-6c67-4c5b-b112-36a304b66dad", }, }; @@ -52,28 +53,28 @@ export const SILENT_TOKEN_RESPONSE: AuthResult = { account: { environment: "login.microsoftonline.com", homeAccountId: - "2995ae49-d9dd-409d-8d62-ba969ce58a81.51178b70-16cc-41b5-bef1-ae1808139065", + "00000000-0000-0000-66f3-3332eca7ea81.3338040d-6c67-4c5b-b112-36a304b66da", idTokenClaims: { - aud: "a076930c-cfc9-4ebd-9607-7963bccbf666", - exp: "1680557128", - graph_url: "https://graph.microsoft.com", - iat: "1680553228", - iss: "https://login.microsoftonline.com/51178b70-16cc-41b5-bef1-ae1808139065/v2.0", - name: "Adele Vance", - nbf: "1680553228", - oid: "2995ae49-d9dd-409d-8d62-ba969ce58a81", - preferred_username: "AdeleV@vc6w6.onmicrosoft.com", - rh: "0.AX0AcIsXUcwWtUG-8a4YCBOQZQyTdqDJz71Olgd5Y7zL9maaAHs.", - sovereignty2: "Global", - sub: "wtxUI1WD2C--Bl8vN1p-P-VgadGud8QSqXD4Vp5i9sc", - tid: "51178b70-16cc-41b5-bef1-ae1808139065", - uti: "39pEKQyYDU6SXjD_phaCAA", ver: "2.0", + iss: "https://login.microsoftonline.com/3338040d-6c67-4c5b-b112-36a304b66dad/v2.0", + sub: "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", + aud: "6cb04018-a3f5-46a7-b995-940c78f5aef3", + exp: 1536361411, + iat: 1536274711, + nbf: 1536274711, + name: "Abe Lincoln", + preferred_username: "AbeLi@microsoft.com", + login_hint: "AbeLiLoginHint", + upn: "AbeLiUpn", + sid: "AbeLiSid", + oid: "00000000-0000-0000-66f3-3332eca7ea81", + tid: "3338040d-6c67-4c5b-b112-36a304b66dad", + nonce: "123523", }, - localAccountId: "2995ae49-d9dd-409d-8d62-ba969ce58a81", - name: "Adele Vance", - tenantId: "51178b70-16cc-41b5-bef1-ae1808139065", - username: "AdeleV@vc6w6.onmicrosoft.com", + localAccountId: "00000000-0000-0000-66f3-3332eca7ea8", + name: "Abe Lincoln", + tenantId: "3338040d-6c67-4c5b-b112-36a304b66dad", + username: "AbeLi@microsoft.com", }, }; @@ -85,21 +86,21 @@ export const NAA_APP_CONSTANTS = { tenantId: "51178b70-16cc-41b5-bef1-ae1808139065", username: "AdeleV@vc6w6.onmicrosoft.com", idTokenClaims: { - aud: "a076930c-cfc9-4ebd-9607-7963bccbf666", - exp: "1680557128", - graph_url: "https://graph.microsoft.com", - iat: "1680553228", - iss: "https://login.microsoftonline.com/51178b70-16cc-41b5-bef1-ae1808139065/v2.0", - name: "Adele Vance", - nbf: "1680553228", - oid: "2995ae49-d9dd-409d-8d62-ba969ce58a81", - preferred_username: "AdeleV@vc6w6.onmicrosoft.com", - rh: "0.AX0AcIsXUcwWtUG-8a4YCBOQZQyTdqDJz71Olgd5Y7zL9maaAHs.", - sovereignty2: "Global", - sub: "wtxUI1WD2C--Bl8vN1p-P-VgadGud8QSqXD4Vp5i9sc", - tid: "51178b70-16cc-41b5-bef1-ae1808139065", - uti: "39pEKQyYDU6SXjD_phaCAA", ver: "2.0", + iss: "https://login.microsoftonline.com/3338040d-6c67-4c5b-b112-36a304b66dad/v2.0", + sub: "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", + aud: "6cb04018-a3f5-46a7-b995-940c78f5aef3", + exp: 1536361411, + iat: 1536274711, + nbf: 1536274711, + name: "Abe Lincoln", + preferred_username: "AbeLi@microsoft.com", + login_hint: "AbeLiLoginHint", + upn: "AbeLiUpn", + sid: "AbeLiSid", + oid: "00000000-0000-0000-66f3-3332eca7ea81", + tid: "3338040d-6c67-4c5b-b112-36a304b66dad", + nonce: "123523", }, }; diff --git a/lib/msal-browser/test/naa/JSRuntime.spec.ts b/lib/msal-browser/test/naa/JSRuntime.spec.ts new file mode 100644 index 0000000000..973f035cc6 --- /dev/null +++ b/lib/msal-browser/test/naa/JSRuntime.spec.ts @@ -0,0 +1,136 @@ +/** + * @jest-environment node + */ + +import MockBridge from "./MockBridge.js"; +import { + BRIDGE_ERROR_USER_INTERACTION_REQUIRED, + INIT_CONTEXT_RESPONSE, + SILENT_TOKEN_RESPONSE, +} from "./BridgeProxyConstants.js"; +import { PublicClientNext } from "../../src/app/PublicClientNext.js"; +import { TEST_CONFIG, TEST_TOKENS } from "../utils/StringConstants.js"; +import { randomFillSync } from "crypto"; +import { TokenClaims } from "@azure/msal-common"; +import { CacheLookupPolicy } from "../../src/index.js"; +import { InteractionRequiredAuthError } from "@azure/msal-common"; + +/** + * Tests Nested App Auth for JS Runtime environment + * + * JS Runtime environment is simulated, but does not have DOM dependencies and some platform API available. + * The node environment is the most similar to JS Runtime available to Jest. + * This is testing E2E Nested App Auth scenario with mock responses. + */ + +describe("JS Runtime Nested App Auth", () => { + let mockBridge: MockBridge; + const globalObj: any = global; + + function deleteGlobalProperty(name: string) { + delete globalObj[name]; + } + + beforeAll(() => { + globalObj.self = globalObj; + // JS Runtime is not a browser, but does have window defined + globalObj.window = globalObj; + + // Remove global properties that can be reset after test that don't have full implementations in JS Runtime + deleteGlobalProperty("crypto"); + deleteGlobalProperty("TextEncoder"); + deleteGlobalProperty("btoa"); + + // Add platform API Nested App Auth depends on + globalObj["crypto"] = { + getRandomValues(dataBuffer: any) { + return randomFillSync(dataBuffer); + }, + }; + + mockBridge = new MockBridge(); + + // Delete nestedAppAuthBridge so that it can be restored after test + deleteGlobalProperty("nestedAppAuthBridge"); + globalObj.nestedAppAuthBridge = mockBridge; + }); + + it("Nested App Auth access token can be acquired", async () => { + mockBridge.addInitContextResponse( + "GetInitContext", + INIT_CONTEXT_RESPONSE + ); + + const pca = await PublicClientNext.createPublicClientApplication({ + auth: { + clientId: TEST_CONFIG.MSAL_CLIENT_ID, + supportsNestedAppAuth: true, + }, + }); + + expect(pca.getActiveAccount()).toBe(null); + + // Validate ssoSilent + mockBridge.addAuthResultResponse("GetToken", SILENT_TOKEN_RESPONSE); + { + const authResult = await pca.ssoSilent({ scopes: ["User.Read"] }); + const idTokenClaims: TokenClaims = authResult.idTokenClaims; + expect(authResult.account.homeAccountId).toBe( + "00000000-0000-0000-66f3-3332eca7ea81.3338040d-6c67-4c5b-b112-36a304b66da" + ); + expect(idTokenClaims.aud).toBe( + "6cb04018-a3f5-46a7-b995-940c78f5aef3" + ); + expect(authResult.fromCache).toBe(false); + expect(authResult.accessToken).toBe(TEST_TOKENS.ACCESS_TOKEN); + } + + // Validate acquireTokenSilent + { + const authResult = await pca.acquireTokenSilent({ + scopes: ["User.Read"], + cacheLookupPolicy: CacheLookupPolicy.Default, + }); + const idTokenClaims: TokenClaims = authResult.idTokenClaims; + expect(authResult.account.homeAccountId).toBe( + "00000000-0000-0000-66f3-3332eca7ea81.3338040d-6c67-4c5b-b112-36a304b66da" + ); + expect(idTokenClaims.aud).toBe( + "6cb04018-a3f5-46a7-b995-940c78f5aef3" + ); + expect(authResult.fromCache).toBe(true); + expect(authResult.accessToken).toBe(TEST_TOKENS.ACCESS_TOKEN); + } + + // Validate error scenario + mockBridge.addErrorResponse( + "GetToken", + BRIDGE_ERROR_USER_INTERACTION_REQUIRED + ); + await expect(() => + pca.acquireTokenSilent({ + scopes: ["Files.Read"], + }) + ).rejects.toThrow(InteractionRequiredAuthError); + + // Validate acquireTokenPopup + mockBridge.addAuthResultResponse( + "GetTokenPopup", + SILENT_TOKEN_RESPONSE + ); + { + const authResult = await pca.acquireTokenPopup({ + scopes: ["User.Read"], + }); + const idTokenClaims: TokenClaims = authResult.idTokenClaims; + expect(authResult.account.homeAccountId).toBe( + "00000000-0000-0000-66f3-3332eca7ea81.3338040d-6c67-4c5b-b112-36a304b66da" + ); + expect(idTokenClaims.aud).toBe( + "6cb04018-a3f5-46a7-b995-940c78f5aef3" + ); + expect(authResult.fromCache).toBe(false); + expect(authResult.accessToken).toBe(TEST_TOKENS.ACCESS_TOKEN); + } + }); +});