From cacfcbaeae54707565be5e9d6530b3982a7080f8 Mon Sep 17 00:00:00 2001 From: Robbie-Microsoft <87724641+Robbie-Microsoft@users.noreply.github.com> Date: Mon, 13 May 2024 10:10:37 -0400 Subject: [PATCH] Fixed bug where dev-provided certificate was not being attached to client assertion (#7088) Fixes [acquireTokenByClientCredential broken for clientCertificate #7082](https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/7082) Applied boy-scout-rule to `ConfidentialClientApplication.spec.ts` (contains unit tests). I've been waiting for a good opportunity to do this. The ConfidentialClientApplication tests are now in line with the other test files: All Managed Identity sources, ClientCredentialClient, OnBehalfOfClient and UsernamePasswordClient. Co-authored-by: @ericcan --- ...-c008b934-0e82-4434-934d-285baed36417.json | 7 + lib/msal-node/src/client/ClientApplication.ts | 40 +- lib/msal-node/test/client/ClientTestUtils.ts | 55 ++ .../ConfidentialClientApplication.spec.ts | 471 +++++++++--------- lib/msal-node/test/utils/CryptoKeys.ts | 33 ++ 5 files changed, 352 insertions(+), 254 deletions(-) create mode 100644 change/@azure-msal-node-c008b934-0e82-4434-934d-285baed36417.json create mode 100644 lib/msal-node/test/utils/CryptoKeys.ts diff --git a/change/@azure-msal-node-c008b934-0e82-4434-934d-285baed36417.json b/change/@azure-msal-node-c008b934-0e82-4434-934d-285baed36417.json new file mode 100644 index 0000000000..446eb28860 --- /dev/null +++ b/change/@azure-msal-node-c008b934-0e82-4434-934d-285baed36417.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Fixed bug where dev-provided certificate was not being attached to client assertion #7088", + "packageName": "@azure/msal-node", + "email": "rginsburg@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-node/src/client/ClientApplication.ts b/lib/msal-node/src/client/ClientApplication.ts index 67c045e355..1ab8df13ba 100644 --- a/lib/msal-node/src/client/ClientApplication.ts +++ b/lib/msal-node/src/client/ClientApplication.ts @@ -457,9 +457,9 @@ export abstract class ClientApplication { serverTelemetryManager: serverTelemetryManager, clientCredentials: { clientSecret: this.clientSecret, - clientAssertion: this.developerProvidedClientAssertion - ? await this.getClientAssertion(discoveredAuthority) - : undefined, + clientAssertion: await this.getClientAssertion( + discoveredAuthority + ), }, libraryInfo: { sku: NodeConstants.MSAL_SKU, @@ -478,22 +478,26 @@ export abstract class ClientApplication { private async getClientAssertion( authority: Authority ): Promise { - this.clientAssertion = ClientAssertion.fromAssertion( - await getClientAssertion( - this.developerProvidedClientAssertion, - this.config.auth.clientId, - authority.tokenEndpoint - ) - ); + if (this.developerProvidedClientAssertion) { + this.clientAssertion = ClientAssertion.fromAssertion( + await getClientAssertion( + this.developerProvidedClientAssertion, + this.config.auth.clientId, + authority.tokenEndpoint + ) + ); + } - return { - assertion: this.clientAssertion.getJwt( - this.cryptoProvider, - this.config.auth.clientId, - authority.tokenEndpoint - ), - assertionType: NodeConstants.JWT_BEARER_ASSERTION_TYPE, - }; + return ( + this.clientAssertion && { + assertion: this.clientAssertion.getJwt( + this.cryptoProvider, + this.config.auth.clientId, + authority.tokenEndpoint + ), + assertionType: NodeConstants.JWT_BEARER_ASSERTION_TYPE, + } + ); } /** diff --git a/lib/msal-node/test/client/ClientTestUtils.ts b/lib/msal-node/test/client/ClientTestUtils.ts index 85623f9db7..1e87320889 100644 --- a/lib/msal-node/test/client/ClientTestUtils.ts +++ b/lib/msal-node/test/client/ClientTestUtils.ts @@ -43,6 +43,9 @@ import { TEST_POP_VALUES, TEST_TOKENS, } from "../test_kit/StringConstants"; +import { Configuration } from "../../src/config/Configuration"; +import { TEST_CONSTANTS } from "../utils/TestConstants"; +import { CryptoKeys } from "../utils/CryptoKeys"; const ACCOUNT_KEYS = "ACCOUNT_KEYS"; const TOKEN_KEYS = "TOKEN_KEYS"; @@ -357,6 +360,58 @@ export class ClientTestUtils { return clientConfig; } + + static async createTestConfidentialClientConfiguration( + clientCapabilities?: Array, + mockNetworkClient?: INetworkModule + ): Promise { + const mockHttpClient = mockNetworkClient || { + sendGetRequestAsync(): T { + return {} as T; + }, + sendPostRequestAsync(): T { + return {} as T; + }, + }; + + const loggerOptions = { + loggerCallback: (): void => {}, + piiLoggingEnabled: true, + logLevel: LogLevel.Verbose, + }; + + const cryptoKeys: CryptoKeys = new CryptoKeys(); + + const confidentialClientConfig: Configuration = { + auth: { + clientId: TEST_CONSTANTS.CLIENT_ID, + authority: TEST_CONSTANTS.AUTHORITY, + // clientSecret, clientAssertion + clientCertificate: { + thumbprint: cryptoKeys.thumbprint, + privateKey: cryptoKeys.privateKey, + }, + knownAuthorities: [TEST_CONSTANTS.AUTHORITY], + cloudDiscoveryMetadata: "", + authorityMetadata: "", + clientCapabilities, + protocolMode: ProtocolMode.AAD, + }, + // broker, cache + system: { + loggerOptions, + networkClient: mockHttpClient, + }, + telemetry: { + application: { + appName: TEST_CONFIG.applicationName, + appVersion: TEST_CONFIG.applicationVersion, + }, + }, + }; + + return confidentialClientConfig; + } } interface checks { diff --git a/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts b/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts index 5ed62f40f0..571baf0a24 100644 --- a/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts +++ b/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts @@ -5,8 +5,6 @@ import { AuthorizationCodeClient, - SilentFlowClient, - RefreshTokenClient, AuthenticationResult, OIDC_DEFAULT_SCOPES, CommonClientCredentialRequest, @@ -14,10 +12,16 @@ import { ClientAuthErrorCodes, AccountEntity, AccountInfo, + createInteractionRequiredAuthError, + InteractionRequiredAuthErrorCodes, + ClientAssertion, } from "@azure/msal-common"; -import { ID_TOKEN_CLAIMS, TEST_CONSTANTS } from "../utils/TestConstants"; import { - AuthError, + DEFAULT_OPENID_CONFIG_RESPONSE, + ID_TOKEN_CLAIMS, + TEST_CONSTANTS, +} from "../utils/TestConstants"; +import { ConfidentialClientApplication, OnBehalfOfRequest, UsernamePasswordRequest, @@ -27,10 +31,8 @@ import { ClientCredentialClient, RefreshTokenRequest, SilentFlowRequest, + ClientApplication, } from "../../src"; - -import * as msalNode from "../../src"; -import { getMsalCommonAutoMock, MSALCommonModule } from "../utils/MockUtils"; import { CAE_CONSTANTS, CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT, @@ -38,114 +40,76 @@ import { TEST_TOKENS, } from "../test_kit/StringConstants"; import { mockNetworkClient } from "../utils/MockNetworkClient"; -import { getClientAssertionCallback } from "./ClientTestUtils"; +import { ClientTestUtils, getClientAssertionCallback } from "./ClientTestUtils"; import { buildAccountFromIdTokenClaims } from "msal-test-utils"; - -const msalCommon: MSALCommonModule = jest.requireActual("@azure/msal-common"); - -jest.mock("../../src/client/ClientCredentialClient"); -jest.mock("../../src/client/OnBehalfOfClient"); -jest.mock("../../src/client/UsernamePasswordClient"); +import { Constants } from "../../src/utils/Constants"; describe("ConfidentialClientApplication", () => { - let appConfig: Configuration = { - auth: { - clientId: TEST_CONSTANTS.CLIENT_ID, - authority: TEST_CONSTANTS.AUTHORITY, - clientSecret: TEST_CONSTANTS.CLIENT_SECRET, - }, - }; + let config: Configuration; + beforeEach(async () => { + config = + await ClientTestUtils.createTestConfidentialClientConfiguration( + undefined, + mockNetworkClient( + DEFAULT_OPENID_CONFIG_RESPONSE.body, + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT + ) + ); + }); afterEach(() => { jest.restoreAllMocks(); }); test("exports a class", () => { - const authApp = new ConfidentialClientApplication(appConfig); - expect(authApp).toBeInstanceOf(ConfidentialClientApplication); + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); + expect(client).toBeInstanceOf(ConfidentialClientApplication); }); describe("auth code flow", () => { test("acquireTokenByAuthorizationCode", async () => { + const acquireTokenByCodeSpy: jest.SpyInstance = jest.spyOn( + ClientApplication.prototype, + "acquireTokenByCode" + ); + const request: AuthorizationCodeRequest = { scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, redirectUri: TEST_CONSTANTS.REDIRECT_URI, code: TEST_CONSTANTS.AUTHORIZATION_CODE, }; - const mockAuthCodeClientInstance = { - includeRedirectUri: false, - acquireToken: jest.fn(), - }; - jest.spyOn( - msalCommon, - "AuthorizationCodeClient" - ).mockImplementation( - () => - mockAuthCodeClientInstance as unknown as AuthorizationCodeClient - ); + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); - const authApp = new ConfidentialClientApplication(appConfig); - await authApp.acquireTokenByCode(request); - expect(AuthorizationCodeClient).toHaveBeenCalledTimes(1); - }); - - test("acquireTokenBySilentFlow", async () => { - const testAccountEntity: AccountEntity = - buildAccountFromIdTokenClaims(ID_TOKEN_CLAIMS); - - const testAccount: AccountInfo = { - ...testAccountEntity.getAccountInfo(), - idTokenClaims: ID_TOKEN_CLAIMS, - idToken: TEST_TOKENS.IDTOKEN_V2, - }; - - const request: SilentFlowRequest = { - scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, - account: testAccount, - authority: TEST_CONFIG.validAuthority, - correlationId: TEST_CONFIG.CORRELATION_ID, - forceRefresh: false, - }; - - const mockSilentFlowClientInstance = { - includeRedirectUri: false, - acquireToken: jest.fn(), - }; - jest.spyOn(msalCommon, "SilentFlowClient").mockImplementation( - () => - mockSilentFlowClientInstance as unknown as SilentFlowClient + const authResult = (await client.acquireTokenByCode( + request + )) as AuthenticationResult; + expect(authResult.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token ); - - const authApp = new ConfidentialClientApplication(appConfig); - await authApp.acquireTokenSilent(request); - expect(SilentFlowClient).toHaveBeenCalledTimes(1); + expect(acquireTokenByCodeSpy).toHaveBeenCalledTimes(1); }); describe("CAE, claims and client capabilities", () => { let createTokenRequestBodySpy: jest.SpyInstance; let client: ConfidentialClientApplication; let authorizationCodeRequest: AuthorizationCodeRequest; - beforeEach(() => { + beforeEach(async () => { createTokenRequestBodySpy = jest.spyOn( AuthorizationCodeClient.prototype, "createTokenRequestBody" ); - const config: Configuration = { - auth: { - clientId: TEST_CONSTANTS.CLIENT_ID, - authority: TEST_CONSTANTS.AUTHORITY, - clientSecret: TEST_CONSTANTS.CLIENT_SECRET, - clientCapabilities: ["cp1", "cp2"], - }, - system: { - networkClient: mockNetworkClient( + const config: Configuration = + await ClientTestUtils.createTestConfidentialClientConfiguration( + ["cp1", "cp2"], + mockNetworkClient( {}, // not needed CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT - ), - }, - }; + ) + ); client = new ConfidentialClientApplication(config); authorizationCodeRequest = { @@ -229,206 +193,241 @@ describe("ConfidentialClientApplication", () => { }); }); - test("acquireTokenByRefreshToken", async () => { - const request: RefreshTokenRequest = { - scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, - refreshToken: TEST_CONSTANTS.REFRESH_TOKEN, + test("acquireTokenBySilentFlow", async () => { + const acquireTokenSilentSpy: jest.SpyInstance = jest.spyOn( + ClientApplication.prototype, + "acquireTokenSilent" + ); + + const testAccountEntity: AccountEntity = + buildAccountFromIdTokenClaims(ID_TOKEN_CLAIMS); + + const testAccount: AccountInfo = { + ...testAccountEntity.getAccountInfo(), + idTokenClaims: ID_TOKEN_CLAIMS, + idToken: TEST_TOKENS.IDTOKEN_V2, }; - const { RefreshTokenClient: mockRefreshTokenClient } = - getMsalCommonAutoMock(); + const request: SilentFlowRequest = { + scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, + account: testAccount, + authority: TEST_CONFIG.validAuthority, + correlationId: TEST_CONFIG.CORRELATION_ID, + forceRefresh: false, + }; - jest.spyOn(msalCommon, "RefreshTokenClient").mockImplementation( - (conf) => new mockRefreshTokenClient(conf) - ); + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); - const fakeAuthResult = {}; - jest.spyOn( - mockRefreshTokenClient.prototype, - "acquireToken" - ).mockImplementation(() => - Promise.resolve(fakeAuthResult as unknown as AuthenticationResult) + await expect(client.acquireTokenSilent(request)).rejects.toMatchObject( + createInteractionRequiredAuthError( + InteractionRequiredAuthErrorCodes.noTokensFound + ) ); - - const authApp = new ConfidentialClientApplication(appConfig); - await authApp.acquireTokenByRefreshToken(request); - expect(RefreshTokenClient).toHaveBeenCalledTimes(1); + expect(acquireTokenSilentSpy).toHaveBeenCalledTimes(1); }); - test("acquireTokenByClientCredential", async () => { - // @ts-ignore - const testProvider: msalCommon.IAppTokenProvider = () => { - // @ts-ignore - return new Promise((resolve) => - resolve({ - accessToken: "accessToken", - expiresInSeconds: 3601, - refreshInSeconds: 1801, - }) - ); - }; - - const configWithExtensibility: Configuration = { - auth: { - clientId: TEST_CONSTANTS.CLIENT_ID, - authority: TEST_CONSTANTS.AUTHORITY, - clientAssertion: "testAssertion", - }, - }; + test("acquireTokenByRefreshToken", async () => { + const acquireTokenByRefreshTokenSpy: jest.SpyInstance = jest.spyOn( + ClientApplication.prototype, + "acquireTokenByRefreshToken" + ); - const request: ClientCredentialRequest = { + const request: RefreshTokenRequest = { scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, - skipCache: false, + refreshToken: TEST_CONSTANTS.REFRESH_TOKEN, }; - const authApp = new ConfidentialClientApplication( - configWithExtensibility - ); - authApp.SetAppTokenProvider(testProvider); + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); - await authApp.acquireTokenByClientCredential(request); - expect(ClientCredentialClient).toHaveBeenCalledTimes(1); + const authResult = (await client.acquireTokenByRefreshToken( + request + )) as AuthenticationResult; + expect(authResult.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(acquireTokenByRefreshTokenSpy).toHaveBeenCalledTimes(1); }); - it.each([ - TEST_CONFIG.TEST_REQUEST_ASSERTION, - getClientAssertionCallback(TEST_CONFIG.TEST_REQUEST_ASSERTION), - ])( - "acquireTokenByClientCredential with client assertion", - async (clientAssertion) => { + describe("client credential flow", () => { + test("acquireTokenByClientCredential", async () => { + const acquireTokenByClientCredentialSpy: jest.SpyInstance = + jest.spyOn( + ConfidentialClientApplication.prototype, + "acquireTokenByClientCredential" + ); + const request: ClientCredentialRequest = { scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, skipCache: false, - clientAssertion: clientAssertion, }; - ClientCredentialClient.prototype.acquireToken = jest.fn( - (request: CommonClientCredentialRequest) => { - expect(request.clientAssertion).not.toBe(undefined); - expect(request.clientAssertion?.assertion).toBe( - TEST_CONFIG.TEST_REQUEST_ASSERTION - ); - expect(request.clientAssertion?.assertionType).toBe( - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" - ); - return Promise.resolve(null); + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); + + const authResult = (await client.acquireTokenByClientCredential( + request + )) as AuthenticationResult; + expect(authResult.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(acquireTokenByClientCredentialSpy).toHaveBeenCalledTimes(1); + }); + + describe("clientAssertion is used to acquire a token after being provided in the request", () => { + beforeEach(() => { + jest.spyOn( + ClientCredentialClient.prototype, + "acquireToken" + ).mockImplementation( + (request: CommonClientCredentialRequest) => { + expect(request.clientAssertion).not.toBe(undefined); + expect(request.clientAssertion?.assertion).toBe( + TEST_CONFIG.TEST_REQUEST_ASSERTION + ); + expect(request.clientAssertion?.assertionType).toBe( + Constants.JWT_BEARER_ASSERTION_TYPE + ); + return Promise.resolve(null); + } + ); + }); + + it.each([ + TEST_CONFIG.TEST_REQUEST_ASSERTION, + getClientAssertionCallback(TEST_CONFIG.TEST_REQUEST_ASSERTION), + ])( + "acquireTokenByClientCredential with client assertion", + async (clientAssertion) => { + const request: ClientCredentialRequest = { + scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, + skipCache: false, + clientAssertion: clientAssertion, + }; + + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); + await client.acquireTokenByClientCredential(request); } ); + }); - const authApp = new ConfidentialClientApplication(appConfig); - await authApp.acquireTokenByClientCredential(request); - } - ); + test("acquireTokenByClientCredential request does not contain OIDC scopes", async () => { + const request: ClientCredentialRequest = { + scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, + skipCache: false, + }; - test("acquireTokenOnBehalfOf", async () => { - const request: OnBehalfOfRequest = { - scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, - oboAssertion: TEST_CONSTANTS.ACCESS_TOKEN, - }; + jest.spyOn( + ClientCredentialClient.prototype, + "acquireToken" + ).mockImplementation((request: CommonClientCredentialRequest) => { + OIDC_DEFAULT_SCOPES.forEach((scope: string) => { + expect(request.scopes).not.toContain(scope); + }); + return Promise.resolve(null); + }); - const onBehalfOfClientSpy = jest.spyOn(msalNode, "OnBehalfOfClient"); + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); - const authApp = new ConfidentialClientApplication(appConfig); - await authApp.acquireTokenOnBehalfOf(request); - expect(onBehalfOfClientSpy).toHaveBeenCalledTimes(1); - }); + // this request will fail because ClientCredentialClient's acquireToken is mocked + // so that the scopes can be examined + await client.acquireTokenByClientCredential(request); + }); - test("acquireTokenByUsernamePassword", async () => { - const request: UsernamePasswordRequest = { - scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, - username: TEST_CONSTANTS.USERNAME, - password: TEST_CONSTANTS.PASSWORD, - }; + test('acquireTokenByClientCredential throws missingTenantIdError if "common", ""organization", or "consumers" was provided as the tenant id', async () => { + const request: ClientCredentialRequest = { + scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, + skipCache: false, + }; - const usernamePasswordClientSpy = jest.spyOn( - msalNode, - "UsernamePasswordClient" - ); + config.auth.authority = TEST_CONSTANTS.DEFAULT_AUTHORITY; // contains "common" + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); - const authApp = new ConfidentialClientApplication(appConfig); - await authApp.acquireTokenByUsernamePassword(request); - expect(usernamePasswordClientSpy).toHaveBeenCalledTimes(1); - }); + await expect( + client.acquireTokenByClientCredential(request) + ).rejects.toMatchObject( + createClientAuthError(ClientAuthErrorCodes.missingTenantIdError) + ); + }); - test('acquireTokenByClientCredential throws missingTenantIdError if "common", ""organization", or "consumers" was provided as the tenant id', async () => { - // @ts-ignore - const testProvider: msalCommon.IAppTokenProvider = () => { - // @ts-ignore - return new Promise((resolve) => - resolve({ - accessToken: "accessToken", - expiresInSeconds: 3601, - refreshInSeconds: 1801, - }) + test("ensures that developer-provided certificate is attached to client assertion", async () => { + const getClientAssertionSpy: jest.SpyInstance = jest.spyOn( + ClientApplication.prototype, + "getClientAssertion" ); - }; - const appConfig: Configuration = { - auth: { - clientId: TEST_CONSTANTS.CLIENT_ID, - authority: TEST_CONSTANTS.DEFAULT_AUTHORITY, // contains "common" - clientAssertion: "testAssertion", - }, - }; + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); - const request: ClientCredentialRequest = { - scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, - skipCache: false, - }; + const request: ClientCredentialRequest = { + scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, + skipCache: false, + }; - const authApp = new ConfidentialClientApplication(appConfig); - authApp.SetAppTokenProvider(testProvider); + const authResult = (await client.acquireTokenByClientCredential( + request + )) as AuthenticationResult; + expect(authResult.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); - await expect( - authApp.acquireTokenByClientCredential(request) - ).rejects.toMatchObject( - createClientAuthError(ClientAuthErrorCodes.missingTenantIdError) - ); + const clientAssertion: ClientAssertion = await getClientAssertionSpy + .mock.results[0].value; + expect(clientAssertion.assertion.length).toBeGreaterThan(1); + expect(clientAssertion.assertionType).toBe( + Constants.JWT_BEARER_ASSERTION_TYPE + ); + }); }); - test("acquireTokenByClientCredential handles AuthErrors as expected", async () => { - const request: ClientCredentialRequest = { + test("acquireTokenOnBehalfOf", async () => { + const acquireTokenOnBehalfOfSpy: jest.SpyInstance = jest.spyOn( + ConfidentialClientApplication.prototype, + "acquireTokenOnBehalfOf" + ); + + const request: OnBehalfOfRequest = { scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, - skipCache: false, + oboAssertion: TEST_CONSTANTS.ACCESS_TOKEN, }; - jest.spyOn(AuthError.prototype, "setCorrelationId"); - - jest.spyOn( - ClientCredentialClient.prototype, - "acquireToken" - ).mockImplementation(() => { - throw new AuthError(); - }); + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); - try { - const authApp = new ConfidentialClientApplication(appConfig); - await authApp.acquireTokenByClientCredential(request); - } catch (e) { - expect(e).toBeInstanceOf(AuthError); - expect(AuthError.prototype.setCorrelationId).toHaveBeenCalledTimes( - 1 - ); - } + const authResult = (await client.acquireTokenOnBehalfOf( + request + )) as AuthenticationResult; + expect(authResult.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(acquireTokenOnBehalfOfSpy).toHaveBeenCalledTimes(1); }); - test("acquireTokenByClientCredential request does not contain OIDC scopes", async () => { - const request: ClientCredentialRequest = { + test("acquireTokenByUsernamePassword", async () => { + const acquireTokenByUsernamePasswordSpy: jest.SpyInstance = jest.spyOn( + ClientApplication.prototype, + "acquireTokenByUsernamePassword" + ); + + const request: UsernamePasswordRequest = { scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, - skipCache: false, + username: TEST_CONSTANTS.USERNAME, + password: TEST_CONSTANTS.PASSWORD, }; - jest.spyOn( - ClientCredentialClient.prototype, - "acquireToken" - ).mockImplementation((request: CommonClientCredentialRequest) => { - OIDC_DEFAULT_SCOPES.forEach((scope: string) => { - expect(request.scopes).not.toContain(scope); - }); - return Promise.resolve(null); - }); + const client: ConfidentialClientApplication = + new ConfidentialClientApplication(config); - const authApp = new ConfidentialClientApplication(appConfig); - await authApp.acquireTokenByClientCredential(request); + const authResult = (await client.acquireTokenByUsernamePassword( + request + )) as AuthenticationResult; + expect(authResult.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(acquireTokenByUsernamePasswordSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/lib/msal-node/test/utils/CryptoKeys.ts b/lib/msal-node/test/utils/CryptoKeys.ts new file mode 100644 index 0000000000..7f0f8516c8 --- /dev/null +++ b/lib/msal-node/test/utils/CryptoKeys.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import crypto from "crypto"; + +export class CryptoKeys { + private _thumbprint: string; + public get thumbprint(): string { + return this._thumbprint; + } + + private _privateKey: string; + public get privateKey(): string { + return this._privateKey; + } + + constructor() { + const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", { + modulusLength: 2048, + publicKeyEncoding: { type: "spki", format: "pem" }, + privateKeyEncoding: { type: "pkcs8", format: "pem" }, + }); + + this._privateKey = privateKey; + + this._thumbprint = crypto + .createHash("sha512") + .update(publicKey) + .digest("hex"); + } +}