diff --git a/change/@azure-msal-browser-73ec5ebe-e4ae-4669-ae10-1f218239df94.json b/change/@azure-msal-browser-73ec5ebe-e4ae-4669-ae10-1f218239df94.json new file mode 100644 index 0000000000..56c003f285 --- /dev/null +++ b/change/@azure-msal-browser-73ec5ebe-e4ae-4669-ae10-1f218239df94.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Instrument additional data points #7543", + "packageName": "@azure/msal-browser", + "email": "kshabelko@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-msal-common-a68a68db-d459-40f8-bdb4-4ea01b1ed588.json b/change/@azure-msal-common-a68a68db-d459-40f8-bdb4-4ea01b1ed588.json new file mode 100644 index 0000000000..d2fd6028ba --- /dev/null +++ b/change/@azure-msal-common-a68a68db-d459-40f8-bdb4-4ea01b1ed588.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Instrument additional data points #7543", + "packageName": "@azure/msal-common", + "email": "kshabelko@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-browser/src/interaction_client/PopupClient.ts b/lib/msal-browser/src/interaction_client/PopupClient.ts index aafccc7e07..10cd676774 100644 --- a/lib/msal-browser/src/interaction_client/PopupClient.ts +++ b/lib/msal-browser/src/interaction_client/PopupClient.ts @@ -103,6 +103,11 @@ export class PopupClient extends StandardInteractionClient { popupWindowParent: request.popupWindowParent ?? window, }; + this.performanceClient.addFields( + { isAsyncPopup: this.config.system.asyncPopups }, + this.correlationId + ); + // asyncPopups flag is true. Acquires token without first opening popup. Popup will be opened later asynchronously. if (this.config.system.asyncPopups) { this.logger.verbose("asyncPopups set to true, acquiring token"); diff --git a/lib/msal-browser/test/interaction_client/PopupClient.spec.ts b/lib/msal-browser/test/interaction_client/PopupClient.spec.ts index 8239a3a4c6..5b45ed75e4 100644 --- a/lib/msal-browser/test/interaction_client/PopupClient.spec.ts +++ b/lib/msal-browser/test/interaction_client/PopupClient.spec.ts @@ -57,7 +57,7 @@ import { InteractionHandler } from "../../src/interaction_handler/InteractionHan import { getDefaultPerformanceClient } from "../utils/TelemetryUtils.js"; import { AuthenticationResult } from "../../src/response/AuthenticationResult.js"; import { BrowserCacheManager } from "../../src/cache/BrowserCacheManager.js"; -import { BrowserAuthErrorCodes } from "../../src/index.js"; +import { BrowserAuthErrorCodes, BrowserUtils } from "../../src/index.js"; import { FetchClient } from "../../src/network/FetchClient.js"; const testPopupWondowDefaults = { @@ -206,6 +206,7 @@ describe("PopupClient", () => { }); it("opens popups asynchronously if configured", async () => { + const perfClient = getDefaultPerformanceClient(); let pca = new PublicClientApplication({ auth: { clientId: TEST_CONFIG.MSAL_CLIENT_ID, @@ -213,6 +214,14 @@ describe("PopupClient", () => { system: { asyncPopups: true, }, + telemetry: { + client: perfClient, + }, + }); + + let resEvents; + perfClient.addPerformanceCallback((events) => { + resEvents = events; }); await pca.initialize(); @@ -237,7 +246,9 @@ describe("PopupClient", () => { //@ts-ignore pca.performanceClient, //@ts-ignore - pca.nativeInternalStorage + pca.nativeInternalStorage, + undefined, + TEST_CONFIG.CORRELATION_ID ); jest.spyOn(PkceGenerator, "generatePkceCodes").mockResolvedValue({ @@ -258,6 +269,10 @@ describe("PopupClient", () => { TEST_CONFIG.TOKEN_TYPE_BEARER as AuthenticationScheme, }; + const rootMeasurement = perfClient.startMeasurement( + "root-measurement", + request.correlationId + ); const popupSpy = jest .spyOn(PopupClient.prototype, "openSizedPopup") .mockImplementation(); @@ -265,6 +280,7 @@ describe("PopupClient", () => { try { await popupClient.acquireToken(request); } catch (e) {} + rootMeasurement.end({ success: true }); expect(popupSpy).toHaveBeenCalled(); expect(popupSpy.mock.calls[0]).toHaveLength(2); expect( @@ -279,9 +295,14 @@ describe("PopupClient", () => { expect(popupSpy.mock.calls[0][0]).toContain( `login_hint=${encodeURIComponent(request.loginHint || "")}` ); + + // @ts-ignore + const event = resEvents[0]; + expect(event.isAsyncPopup).toBeTruthy(); }); it("calls native broker if server responds with accountId", async () => { + const perfClient = getDefaultPerformanceClient(); pca = new PublicClientApplication({ auth: { clientId: TEST_CONFIG.MSAL_CLIENT_ID, @@ -289,6 +310,14 @@ describe("PopupClient", () => { system: { allowPlatformBroker: true, }, + telemetry: { + client: perfClient, + }, + }); + + let resEvents; + perfClient.addPerformanceCallback((events) => { + resEvents = events; }); await pca.initialize(); @@ -370,7 +399,7 @@ describe("PopupClient", () => { //@ts-ignore pca.logger, 2000, - getDefaultPerformanceClient() + perfClient ); //@ts-ignore popupClient = new PopupClient( @@ -392,11 +421,20 @@ describe("PopupClient", () => { pca.nativeInternalStorage, nativeMessageHandler ); + const correlationId = BrowserUtils.createGuid(); + const rootMeasurement = perfClient.startMeasurement( + "root-measurement", + correlationId + ); const tokenResp = await popupClient.acquireToken({ redirectUri: TEST_URIS.TEST_REDIR_URI, scopes: TEST_CONFIG.DEFAULT_SCOPES, + correlationId, }); + rootMeasurement.end({ success: true }); expect(tokenResp).toEqual(testTokenResponse); + // @ts-ignore + expect(resEvents[0].isAsyncPopup).toBeFalsy(); }); it("throws if server responds with accountId but extension message handler is not instantiated", async () => { diff --git a/lib/msal-common/apiReview/msal-common.api.md b/lib/msal-common/apiReview/msal-common.api.md index c783e94717..04dd45cc81 100644 --- a/lib/msal-common/apiReview/msal-common.api.md +++ b/lib/msal-common/apiReview/msal-common.api.md @@ -3062,6 +3062,15 @@ export type PerformanceEvent = { retryError?: string; embeddedClientId?: string; embeddedRedirectUri?: string; + isAsyncPopup?: boolean; + rtExpiresOnMs?: number; + sidFromClaims?: boolean; + sidFromRequest?: boolean; + loginHintFromRequest?: boolean; + loginHintFromUpn?: boolean; + loginHintFromClaim?: boolean; + domainHintFromRequest?: boolean; + prompt?: string; }; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration @@ -4273,12 +4282,12 @@ const X_MS_LIB_CAPABILITY = "x-ms-lib-capability"; // src/client/AuthorizationCodeClient.ts:229:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/client/AuthorizationCodeClient.ts:307:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/client/AuthorizationCodeClient.ts:507:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/client/AuthorizationCodeClient.ts:730:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/client/AuthorizationCodeClient.ts:790:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/client/AuthorizationCodeClient.ts:763:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/client/AuthorizationCodeClient.ts:823:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/client/RefreshTokenClient.ts:193:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/client/RefreshTokenClient.ts:277:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/client/RefreshTokenClient.ts:278:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/client/RefreshTokenClient.ts:337:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/client/RefreshTokenClient.ts:286:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/client/RefreshTokenClient.ts:287:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/client/RefreshTokenClient.ts:346:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/client/SilentFlowClient.ts:172:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/config/ClientConfiguration.ts:50:5 - (ae-forgotten-export) The symbol "ClientCredentials" needs to be exported by the entry point index.d.ts // src/config/ClientConfiguration.ts:51:5 - (ae-forgotten-export) The symbol "LibraryInfo" needs to be exported by the entry point index.d.ts diff --git a/lib/msal-common/src/client/AuthorizationCodeClient.ts b/lib/msal-common/src/client/AuthorizationCodeClient.ts index 13f7b2670a..2366f75936 100644 --- a/lib/msal-common/src/client/AuthorizationCodeClient.ts +++ b/lib/msal-common/src/client/AuthorizationCodeClient.ts @@ -571,8 +571,17 @@ export class AuthorizationCodeClient extends BaseClient { if (request.domainHint) { parameterBuilder.addDomainHint(request.domainHint); + this.performanceClient?.addFields( + { domainHintFromRequest: true }, + correlationId + ); } + this.performanceClient?.addFields( + { prompt: request.prompt }, + correlationId + ); + // Add sid or loginHint with preference for login_hint claim (in request) -> sid -> loginHint (upn/email) -> username of AccountInfo object if (request.prompt !== PromptValue.SELECT_ACCOUNT) { // AAD will throw if prompt=select_account is passed with an account hint @@ -582,6 +591,10 @@ export class AuthorizationCodeClient extends BaseClient { "createAuthCodeUrlQueryString: Prompt is none, adding sid from request" ); parameterBuilder.addSid(request.sid); + this.performanceClient?.addFields( + { sidFromRequest: true }, + correlationId + ); } else if (request.account) { const accountSid = this.extractAccountSid(request.account); let accountLoginHintClaim = this.extractLoginHint( @@ -601,6 +614,10 @@ export class AuthorizationCodeClient extends BaseClient { "createAuthCodeUrlQueryString: login_hint claim present on account" ); parameterBuilder.addLoginHint(accountLoginHintClaim); + this.performanceClient?.addFields( + { loginHintFromClaim: true }, + correlationId + ); try { const clientInfo = buildClientInfoFromHomeAccountId( request.account.homeAccountId @@ -620,6 +637,10 @@ export class AuthorizationCodeClient extends BaseClient { "createAuthCodeUrlQueryString: Prompt is none, adding sid from account" ); parameterBuilder.addSid(accountSid); + this.performanceClient?.addFields( + { sidFromClaim: true }, + correlationId + ); try { const clientInfo = buildClientInfoFromHomeAccountId( request.account.homeAccountId @@ -636,12 +657,20 @@ export class AuthorizationCodeClient extends BaseClient { ); parameterBuilder.addLoginHint(request.loginHint); parameterBuilder.addCcsUpn(request.loginHint); + this.performanceClient?.addFields( + { loginHintFromRequest: true }, + correlationId + ); } else if (request.account.username) { // Fallback to account username if provided this.logger.verbose( "createAuthCodeUrlQueryString: Adding login_hint from account" ); parameterBuilder.addLoginHint(request.account.username); + this.performanceClient?.addFields( + { loginHintFromUpn: true }, + correlationId + ); try { const clientInfo = buildClientInfoFromHomeAccountId( request.account.homeAccountId @@ -659,6 +688,10 @@ export class AuthorizationCodeClient extends BaseClient { ); parameterBuilder.addLoginHint(request.loginHint); parameterBuilder.addCcsUpn(request.loginHint); + this.performanceClient?.addFields( + { loginHintFromRequest: true }, + correlationId + ); } } else { this.logger.verbose( diff --git a/lib/msal-common/src/client/RefreshTokenClient.ts b/lib/msal-common/src/client/RefreshTokenClient.ts index 3b5c2ffadc..8bf8236030 100644 --- a/lib/msal-common/src/client/RefreshTokenClient.ts +++ b/lib/msal-common/src/client/RefreshTokenClient.ts @@ -230,6 +230,10 @@ export class RefreshTokenClient extends BaseClient { DEFAULT_REFRESH_TOKEN_EXPIRATION_OFFSET_SECONDS ) ) { + this.performanceClient?.addFields( + { rtExpiresOnMs: Number(refreshToken.expiresOn) }, + request.correlationId + ); throw createInteractionRequiredAuthError( InteractionRequiredAuthErrorCodes.refreshTokenExpired ); @@ -256,16 +260,21 @@ export class RefreshTokenClient extends BaseClient { request.correlationId )(refreshTokenRequest); } catch (e) { - if ( - e instanceof InteractionRequiredAuthError && - e.subError === InteractionRequiredAuthErrorCodes.badToken - ) { - // Remove bad refresh token from cache - this.logger.verbose( - "acquireTokenWithRefreshToken: bad refresh token, removing from cache" + if (e instanceof InteractionRequiredAuthError) { + this.performanceClient?.addFields( + { rtExpiresOnMs: Number(refreshToken.expiresOn) }, + request.correlationId ); - const badRefreshTokenKey = generateCredentialKey(refreshToken); - this.cacheManager.removeRefreshToken(badRefreshTokenKey); + + if (e.subError === InteractionRequiredAuthErrorCodes.badToken) { + // Remove bad refresh token from cache + this.logger.verbose( + "acquireTokenWithRefreshToken: bad refresh token, removing from cache" + ); + const badRefreshTokenKey = + generateCredentialKey(refreshToken); + this.cacheManager.removeRefreshToken(badRefreshTokenKey); + } } throw e; diff --git a/lib/msal-common/src/telemetry/performance/PerformanceEvent.ts b/lib/msal-common/src/telemetry/performance/PerformanceEvent.ts index e98dd38744..2b8a4007eb 100644 --- a/lib/msal-common/src/telemetry/performance/PerformanceEvent.ts +++ b/lib/msal-common/src/telemetry/performance/PerformanceEvent.ts @@ -868,6 +868,19 @@ export type PerformanceEvent = { embeddedClientId?: string; embeddedRedirectUri?: string; + + isAsyncPopup?: boolean; + + rtExpiresOnMs?: number; + + sidFromClaims?: boolean; + sidFromRequest?: boolean; + loginHintFromRequest?: boolean; + loginHintFromUpn?: boolean; + loginHintFromClaim?: boolean; + domainHintFromRequest?: boolean; + + prompt?: string; }; export type PerformanceEventContext = { diff --git a/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts b/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts index a0ca9b904f..9da0fd7805 100644 --- a/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts +++ b/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts @@ -48,6 +48,7 @@ import { createClientConfigurationError, } from "../../src/index.js"; import { ProtocolMode } from "../../src/authority/ProtocolMode.js"; +import { MockPerformanceClient } from "../telemetry/PerformanceClient.spec.js"; describe("AuthorizationCodeClient unit tests", () => { afterEach(() => { @@ -300,7 +301,12 @@ describe("AuthorizationCodeClient unit tests", () => { const config: ClientConfiguration = await ClientTestUtils.createTestClientConfiguration(); - const client = new AuthorizationCodeClient(config); + const mockPerfClient = new MockPerformanceClient(); + const client = new AuthorizationCodeClient(config, mockPerfClient); + let resEvents; + mockPerfClient.addPerformanceCallback((events) => { + resEvents = events; + }); const authCodeUrlRequest: CommonAuthorizationUrlRequest = { redirectUri: TEST_URIS.TEST_REDIRECT_URI_LOCALHOST, @@ -315,6 +321,10 @@ describe("AuthorizationCodeClient unit tests", () => { authority: TEST_CONFIG.validAuthority, responseMode: ResponseMode.FRAGMENT, }; + const rootMeasurement = mockPerfClient.startMeasurement( + "root-measurement", + authCodeUrlRequest.correlationId + ); const loginUrl = await client.getAuthCodeUrl(authCodeUrlRequest); expect( loginUrl.includes( @@ -330,6 +340,12 @@ describe("AuthorizationCodeClient unit tests", () => { )}` ) ).toBe(true); + rootMeasurement.end({ success: true }); + // @ts-ignore + const event = resEvents[0]; + expect(event.loginHintFromRequest).toBeTruthy(); + expect(event.loginHintFromUpn).toBeFalsy(); + expect(event.loginHintFromClaim).toBeFalsy(); }); it("Adds CCS entry if account is provided", async () => { @@ -406,9 +422,14 @@ describe("AuthorizationCodeClient unit tests", () => { "getEndpointMetadataFromNetwork" ).mockResolvedValue(ALTERNATE_OPENID_CONFIG_RESPONSE.body); + const mockPerfClient = new MockPerformanceClient(); const config: ClientConfiguration = await ClientTestUtils.createTestClientConfiguration(); - const client = new AuthorizationCodeClient(config); + const client = new AuthorizationCodeClient(config, mockPerfClient); + let resEvents; + mockPerfClient.addPerformanceCallback((events) => { + resEvents = events; + }); const testAccount = TEST_ACCOUNT_INFO; // @ts-ignore const testTokenClaims: Required< @@ -450,6 +471,10 @@ describe("AuthorizationCodeClient unit tests", () => { authority: TEST_CONFIG.validAuthority, responseMode: ResponseMode.FRAGMENT, }; + const rootMeasurement = mockPerfClient.startMeasurement( + "root-measurement", + authCodeUrlRequest.correlationId + ); const loginUrl = await client.getAuthCodeUrl(authCodeUrlRequest); expect( loginUrl.includes( @@ -472,6 +497,16 @@ describe("AuthorizationCodeClient unit tests", () => { )}` ) ).toBe(true); + + rootMeasurement.end({ success: true }); + // @ts-ignore + const event = resEvents[0]; + expect(event.loginHintFromUpn).toBeFalsy(); + expect(event.loginHintFromClaim).toBeTruthy(); + expect(event.loginHintFromRequest).toBeFalsy(); + expect(event.domainHintFromRequest).toBeFalsy(); + expect(event.sidFromClaim).toBeFalsy(); + expect(event.sidFromRequest).toBeFalsy(); }); it("skips login_hint claim if domainHint param is set", async () => { @@ -480,9 +515,14 @@ describe("AuthorizationCodeClient unit tests", () => { "getEndpointMetadataFromNetwork" ).mockResolvedValue(ALTERNATE_OPENID_CONFIG_RESPONSE.body); + const mockPerfClient = new MockPerformanceClient(); const config: ClientConfiguration = await ClientTestUtils.createTestClientConfiguration(); - const client = new AuthorizationCodeClient(config); + const client = new AuthorizationCodeClient(config, mockPerfClient); + let resEvents; + mockPerfClient.addPerformanceCallback((events) => { + resEvents = events; + }); const testAccount = TEST_ACCOUNT_INFO; // @ts-ignore const testTokenClaims: Required< @@ -524,6 +564,10 @@ describe("AuthorizationCodeClient unit tests", () => { responseMode: ResponseMode.FRAGMENT, domainHint: TEST_CONFIG.DOMAIN_HINT, }; + const rootMeasurement = mockPerfClient.startMeasurement( + "root-measurement", + authCodeUrlRequest.correlationId + ); const loginUrl = await client.getAuthCodeUrl(authCodeUrlRequest); expect( loginUrl.includes( @@ -553,6 +597,16 @@ describe("AuthorizationCodeClient unit tests", () => { )}` ) ).toBe(true); + + rootMeasurement.end({ success: true }); + // @ts-ignore + const event = resEvents[0]; + expect(event.loginHintFromUpn).toBeTruthy(); + expect(event.loginHintFromClaim).toBeFalsy(); + expect(event.loginHintFromRequest).toBeFalsy(); + expect(event.domainHintFromRequest).toBeTruthy(); + expect(event.sidFromClaim).toBeFalsy(); + expect(event.sidFromRequest).toBeFalsy(); }); it("picks up both loginHint and domainHint params", async () => { @@ -639,7 +693,12 @@ describe("AuthorizationCodeClient unit tests", () => { const config: ClientConfiguration = await ClientTestUtils.createTestClientConfiguration(); - const client = new AuthorizationCodeClient(config); + const mockPerfClient = new MockPerformanceClient(); + const client = new AuthorizationCodeClient(config, mockPerfClient); + let resEvents; + mockPerfClient.addPerformanceCallback((events) => { + resEvents = events; + }); const authCodeUrlRequest: CommonAuthorizationUrlRequest = { redirectUri: TEST_URIS.TEST_REDIRECT_URI_LOCALHOST, @@ -655,6 +714,10 @@ describe("AuthorizationCodeClient unit tests", () => { authority: TEST_CONFIG.validAuthority, responseMode: ResponseMode.FRAGMENT, }; + const rootMeasurement = mockPerfClient.startMeasurement( + "root-measurement", + authCodeUrlRequest.correlationId + ); const loginUrl = await client.getAuthCodeUrl(authCodeUrlRequest); expect(loginUrl).toEqual( expect.not.arrayContaining([ @@ -668,6 +731,17 @@ describe("AuthorizationCodeClient unit tests", () => { )}` ) ).toBe(true); + + rootMeasurement.end({ success: true }); + // @ts-ignore + const event = resEvents[0]; + expect(event.loginHintFromRequest).toBeFalsy(); + expect(event.loginHintFromClaim).toBeFalsy(); + expect(event.loginHintFromUpn).toBeFalsy(); + expect(event.domainHintFromRequest).toBeFalsy(); + expect(event.sidFromRequest).toBeTruthy(); + expect(event.sidFromRequest).toBeTruthy(); + expect(event.prompt).toEqual(PromptValue.NONE); }); it("Prefers loginHint over sid if both provided and prompt!=None", async () => { @@ -713,9 +787,14 @@ describe("AuthorizationCodeClient unit tests", () => { "getEndpointMetadataFromNetwork" ).mockResolvedValue(ALTERNATE_OPENID_CONFIG_RESPONSE.body); + const mockPerfClient = new MockPerformanceClient(); const config: ClientConfiguration = await ClientTestUtils.createTestClientConfiguration(); - const client = new AuthorizationCodeClient(config); + const client = new AuthorizationCodeClient(config, mockPerfClient); + let resEvents; + mockPerfClient.addPerformanceCallback((events) => { + resEvents = events; + }); const authCodeUrlRequest: CommonAuthorizationUrlRequest = { redirectUri: TEST_URIS.TEST_REDIRECT_URI_LOCALHOST, @@ -730,11 +809,26 @@ describe("AuthorizationCodeClient unit tests", () => { authority: TEST_CONFIG.validAuthority, responseMode: ResponseMode.FRAGMENT, }; + const rootMeasurement = mockPerfClient.startMeasurement( + "root-measurement", + authCodeUrlRequest.correlationId + ); const loginUrl = await client.getAuthCodeUrl(authCodeUrlRequest); expect(loginUrl.includes(`${AADServerParamKeys.LOGIN_HINT}=`)).toBe( false ); expect(loginUrl.includes(`${AADServerParamKeys.SID}=`)).toBe(false); + + rootMeasurement.end({ success: true }); + // @ts-ignore + const event = resEvents[0]; + expect(event.loginHintFromUpn).toBeFalsy(); + expect(event.loginHintFromClaim).toBeFalsy(); + expect(event.loginHintFromRequest).toBeFalsy(); + expect(event.domainHintFromRequest).toBeFalsy(); + expect(event.sidFromClaim).toBeFalsy(); + expect(event.sidFromRequest).toBeFalsy(); + expect(event.prompt).toEqual(PromptValue.LOGIN); }); it("Prefers loginHint over Account if both provided and account does not have token claims", async () => { diff --git a/lib/msal-common/test/client/RefreshTokenClient.spec.ts b/lib/msal-common/test/client/RefreshTokenClient.spec.ts index 8d86efda19..1dcf66ccd4 100644 --- a/lib/msal-common/test/client/RefreshTokenClient.spec.ts +++ b/lib/msal-common/test/client/RefreshTokenClient.spec.ts @@ -61,6 +61,7 @@ import { ProtocolMode } from "../../src/authority/ProtocolMode.js"; import * as TimeUtils from "../../src/utils/TimeUtils.js"; import { buildAccountFromIdTokenClaims } from "msal-test-utils"; import { generateCredentialKey } from "../../src/cache/utils/CacheHelpers.js"; +import { MockPerformanceClient } from "../telemetry/PerformanceClient.spec.js"; const testAccountEntity: AccountEntity = new AccountEntity(); testAccountEntity.homeAccountId = `${TEST_DATA_CLIENT_INFO.TEST_UID}.${TEST_DATA_CLIENT_INFO.TEST_UTID}`; @@ -1321,20 +1322,24 @@ describe("RefreshTokenClient unit tests", () => { }; const config = await ClientTestUtils.createTestClientConfiguration(); + const rtExpiresOn = TimeUtils.nowSeconds() - 48 * 60 * 60; await config.storageInterface!.setRefreshTokenCredential( { ...testRefreshTokenEntity, - expiresOn: ( - TimeUtils.nowSeconds() - - 48 * 60 * 60 - ).toString(), // Set expiration to yesterday + expiresOn: rtExpiresOn.toString(), // Set expiration to yesterday }, TEST_CONFIG.CORRELATION_ID ); - const client = new RefreshTokenClient( - config, - stubPerformanceClient + const mockPerfClient = new MockPerformanceClient(); + const client = new RefreshTokenClient(config, mockPerfClient); + const rootMeasurement = mockPerfClient.startMeasurement( + "test-measurement", + TEST_CONFIG.CORRELATION_ID ); + let resEvents; + mockPerfClient.addPerformanceCallback((events) => { + resEvents = events; + }); await expect( client.acquireTokenByRefreshToken(tokenRequest) ).rejects.toMatchObject( @@ -1342,6 +1347,9 @@ describe("RefreshTokenClient unit tests", () => { InteractionRequiredAuthErrorCodes.refreshTokenExpired ) ); + rootMeasurement.end({ success: false }); + // @ts-ignore + expect(resEvents[0].rtExpiresOnMs).toEqual(rtExpiresOn); }); it("Throws error if cached RT expiration is within provided offset", async () => { @@ -1383,15 +1391,26 @@ describe("RefreshTokenClient unit tests", () => { testAccountEntity, TEST_CONFIG.CORRELATION_ID ); + const rtExpiresOn = TimeUtils.nowSeconds() + 60 * 60; + const rtEntity = { + ...testRefreshTokenEntity, + expiresOn: rtExpiresOn.toString(), + }; await config.storageInterface!.setRefreshTokenCredential( - testRefreshTokenEntity, + rtEntity, TEST_CONFIG.CORRELATION_ID ); config.storageInterface!.setAppMetadata(testAppMetadata); - const client = new RefreshTokenClient( - config, - stubPerformanceClient + const mockPerfClient = new MockPerformanceClient(); + const rootMeasurement = mockPerfClient.startMeasurement( + "test-measurement", + TEST_CONFIG.CORRELATION_ID ); + let resEvents; + mockPerfClient.addPerformanceCallback((events) => { + resEvents = events; + }); + const client = new RefreshTokenClient(config, mockPerfClient); const testAccount: AccountInfo = buildAccountFromIdTokenClaims(ID_TOKEN_CLAIMS).getAccountInfo(); testAccount.idTokenClaims = ID_TOKEN_CLAIMS; @@ -1420,15 +1439,13 @@ describe("RefreshTokenClient unit tests", () => { forceRefresh: false, }; - const badRefreshTokenKey = generateCredentialKey( - testRefreshTokenEntity - ); + const badRefreshTokenKey = generateCredentialKey(rtEntity); expect( config.storageInterface!.getRefreshTokenCredential( badRefreshTokenKey ) - ).toBe(testRefreshTokenEntity); + ).toBe(rtEntity); await expect( client.acquireTokenByRefreshToken(silentFlowRequest) @@ -1439,6 +1456,10 @@ describe("RefreshTokenClient unit tests", () => { badRefreshTokenKey ) ).toBe(null); + + rootMeasurement.end({ success: false }); + // @ts-ignore + expect(resEvents[0].rtExpiresOnMs).toEqual(rtExpiresOn); }); }); describe("Telemetry protocol mode tests", () => {