Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
749f002
Initial plan
Copilot Dec 3, 2025
2ec6f71
Add telemetry performance event fields for platform broker integration
Copilot Dec 3, 2025
d1ba090
Update PlatformAuthInteractionClient telemetry instrumentation
Copilot Dec 3, 2025
26b4174
Add StandardController telemetry for acquireToken methods (partial)
Copilot Dec 3, 2025
445e38f
Complete StandardController telemetry instrumentation
Copilot Dec 3, 2025
b988285
Add beachball change files for msal-v5 branch
Copilot Dec 3, 2025
5d2209e
Remove getAccountType function and restore original telemetry fields
Copilot Dec 3, 2025
67af560
Update lib/msal-browser/src/controllers/StandardController.ts
sameerag Dec 3, 2025
10bc6e7
Update NativeAuthError test for PERSISTENT_ERROR behavior
Copilot Dec 3, 2025
92d5766
Update lib/msal-common/src/telemetry/performance/PerformanceEvent.ts
sameerag Dec 3, 2025
9f988d6
Update change/@azure-msal-browser-v5-telemetry-cherry-pick.json
sameerag Dec 3, 2025
2e190fe
Update change/@azure-msal-common-v5-telemetry-cherry-pick.json
sameerag Dec 3, 2025
1d8f086
Update lib/msal-browser/src/interaction_client/PlatformAuthInteractio…
sameerag Dec 3, 2025
9e82d1b
Remove misleading brokerErrorCode for cache miss scenario
Copilot Dec 3, 2025
58cd7a2
Add performance event validation tests to PlatformAuthInteractionClient
Copilot Dec 3, 2025
ebd6f93
Update change/@azure-msal-browser-v5-telemetry-cherry-pick.json
sameerag Dec 3, 2025
a7e8b69
Update change/@azure-msal-browser-v5-telemetry-cherry-pick.json
sameerag Dec 3, 2025
56cd917
Update change/@azure-msal-common-v5-telemetry-cherry-pick.json
sameerag Dec 3, 2025
b889f1b
Update changefile format to reference PR #7991
Copilot Dec 3, 2025
7331407
Add telemetry validation to acquireTokenRedirect platform broker test
Copilot Dec 3, 2025
3e06f5a
Merge branch 'msal-v5' into copilot/cherry-pick-js-telemetry-params
sameerag Dec 4, 2025
22ff8b6
Update apiReview for common
sameerag Dec 4, 2025
f04ba36
Update change/@azure-msal-common-v5-telemetry-cherry-pick.json
sameerag Dec 4, 2025
fe9508d
Update change/@azure-msal-common-v5-telemetry-cherry-pick.json
sameerag Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions change/@azure-msal-browser-v5-telemetry-cherry-pick.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "[v5]Add Platform Telemetry [#8175](https://github.com/AzureAD/microsoft-authentication-library-for-js/pull/8175)",
"packageName": "@azure/msal-browser",
"email": "[email protected]",
"dependentChangeType": "patch"
}
7 changes: 7 additions & 0 deletions change/@azure-msal-common-v5-telemetry-cherry-pick.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "[v5]Add Platform Telemetry [#8175](https://github.com/AzureAD/microsoft-authentication-library-for-js/pull/8175)
"packageName": "@azure/msal-common",
"email": "[email protected]",
"dependentChangeType": "patch"
}
50 changes: 45 additions & 5 deletions lib/msal-browser/src/controllers/StandardController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,9 @@ export class StandardController implements IController {
"handleRedirectPromise - acquiring token from native platform",
correlationId
);
rootMeasurement.add({
isPlatformBrokerRequest: true,
});
const nativeClient = new PlatformAuthInteractionClient(
this.config,
this.browserStorage,
Expand Down Expand Up @@ -650,6 +653,9 @@ export class StandardController implements IController {
this.platformAuthProvider &&
this.canUsePlatformBroker(request)
) {
atrMeasurement.add({
isPlatformBrokerRequest: true,
});
const nativeClient = new PlatformAuthInteractionClient(
this.config,
this.browserStorage,
Expand All @@ -664,14 +670,19 @@ export class StandardController implements IController {
this.nativeInternalStorage,
correlationId
);

result = nativeClient
.acquireTokenRedirect(request, atrMeasurement)
.catch((e: AuthError) => {
atrMeasurement.add({
brokerErrorName: e.name,
brokerErrorCode: e.errorCode,
});
if (
e instanceof NativeAuthError &&
isFatalNativeAuthError(e)
) {
this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt
this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt platform broker calls
const redirectClient =
this.createRedirectClient(correlationId);
return redirectClient.acquireToken(request);
Expand Down Expand Up @@ -771,6 +782,9 @@ export class StandardController implements IController {
const pkce = this.getPreGeneratedPkceCodes(correlationId);

if (this.canUsePlatformBroker(request)) {
atPopupMeasurement.add({
isPlatformBrokerRequest: true,
});
result = this.acquireTokenNative(
{
...request,
Expand All @@ -790,11 +804,15 @@ export class StandardController implements IController {
return response;
})
.catch((e: AuthError) => {
atPopupMeasurement.add({
brokerErrorName: e.name,
brokerErrorCode: e.errorCode,
});
if (
e instanceof NativeAuthError &&
isFatalNativeAuthError(e)
) {
this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt
this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt platform broker calls
const popupClient =
this.createPopupClient(correlationId);
return popupClient.acquireToken(request, pkce);
Expand Down Expand Up @@ -942,10 +960,17 @@ export class StandardController implements IController {
let result: Promise<AuthenticationResult>;

if (this.canUsePlatformBroker(validRequest)) {
this.ssoSilentMeasurement?.add({
isPlatformBrokerRequest: true,
});
result = this.acquireTokenNative(
validRequest,
ApiId.ssoSilent
).catch((e: AuthError) => {
this.ssoSilentMeasurement?.add({
brokerErrorName: e.name,
brokerErrorCode: e.errorCode,
});
// If native token acquisition fails for availability reasons fallback to standard flow
if (e instanceof NativeAuthError && isFatalNativeAuthError(e)) {
this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt
Expand Down Expand Up @@ -1108,6 +1133,9 @@ export class StandardController implements IController {
if (
this.canUsePlatformBroker(request, request.nativeAccountId)
) {
atbcMeasurement.add({
isPlatformBrokerRequest: true,
});
const result = await this.acquireTokenNative(
{
...request,
Expand All @@ -1123,6 +1151,10 @@ export class StandardController implements IController {
) {
this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt
}
atbcMeasurement.add({
brokerErrorName: e.name,
brokerErrorCode: e.errorCode,
});
throw e;
});
atbcMeasurement.end(
Expand Down Expand Up @@ -1190,7 +1222,6 @@ export class StandardController implements IController {
this.acquireTokenByCodeAsyncMeasurement?.end({
success: true,
fromCache: response.fromCache,
isNativeBroker: response.fromPlatformBroker,
});
return response;
})
Expand Down Expand Up @@ -1878,7 +1909,6 @@ export class StandardController implements IController {
{
success: true,
fromCache: result.fromCache,
isNativeBroker: result.fromPlatformBroker,
accessTokenSize: result.accessToken.length,
idTokenSize: result.idToken.length,
},
Expand Down Expand Up @@ -2117,7 +2147,6 @@ export class StandardController implements IController {
this.performanceClient.addFields(
{
fromCache: response.fromCache,
isNativeBroker: response.fromPlatformBroker,
},
request.correlationId
);
Expand Down Expand Up @@ -2167,12 +2196,23 @@ export class StandardController implements IController {
"acquireTokenSilent - attempting to acquire token from native platform",
silentRequest.correlationId
);
this.performanceClient.addFields(
{ isPlatformBrokerRequest: true },
silentRequest.correlationId
);
return this.acquireTokenNative(
silentRequest,
ApiId.acquireTokenSilent_silentFlow,
silentRequest.account.nativeAccountId,
cacheLookupPolicy
).catch(async (e: AuthError) => {
this.performanceClient.addFields(
{
brokerErrorName: e.name,
brokerErrorCode: e.errorCode,
},
silentRequest.correlationId
);
// If native token acquisition fails for availability reasons fallback to web flow
if (e instanceof NativeAuthError && isFatalNativeAuthError(e)) {
this.logger.verbose(
Expand Down
3 changes: 1 addition & 2 deletions lib/msal-browser/src/error/NativeAuthError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ export function isFatalNativeAuthError(error: NativeAuthError): boolean {
if (
error.ext &&
error.ext.status &&
(error.ext.status === NativeStatusCodes.PERSISTENT_ERROR ||
error.ext.status === NativeStatusCodes.DISABLED)
error.ext.status === NativeStatusCodes.DISABLED
) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient {
// start the perf measurement
const nativeATMeasurement = this.performanceClient.startMeasurement(
BrowserPerformanceEvents.NativeInteractionClientAcquireToken,
request.correlationId
this.correlationId
);
const reqTimestamp = TimeUtils.nowSeconds();

Expand Down Expand Up @@ -200,6 +200,9 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient {
"MSAL internal Cache does not contain tokens, return error as per cache policy",
this.correlationId
);
nativeATMeasurement.end({
success: false,
});
throw e;
}
// continue with a native call for any and all errors
Expand All @@ -220,25 +223,36 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient {
.then((result: AuthenticationResult) => {
nativeATMeasurement.end({
success: true,
isNativeBroker: true,
requestId: result.requestId,
});
serverTelemetryManager.clearNativeBrokerErrorCode();
if (this.performanceClient && this.correlationId) {
this.performanceClient.addFields(
{ isNativeBroker: true },
this.correlationId
);
}
return result;
})
.catch((error: AuthError) => {
nativeATMeasurement.end({
success: false,
errorCode: error.errorCode,
subErrorCode: error.subError,
isNativeBroker: true,
});
throw error;
});
} catch (e) {
if (e instanceof NativeAuthError) {
serverTelemetryManager.setNativeBrokerErrorCode(e.errorCode);
}
nativeATMeasurement.end({
success: false,
...(e instanceof NativeAuthError && {
brokerErrorName: e.name,
brokerErrorCode: e.errorCode,
}),
});
throw e;
}
}
Expand Down Expand Up @@ -284,7 +298,7 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient {
{
nativeAccountId,
},
request.correlationId
this.correlationId
);

if (!account) {
Expand Down Expand Up @@ -1144,7 +1158,7 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient {
embeddedClientId: child_client_id,
embeddedRedirectUri: child_redirect_uri,
},
request.correlationId
this.correlationId
);
}
}
8 changes: 8 additions & 0 deletions lib/msal-browser/src/protocol/Authorize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ async function getStandardParameters(
// signal ests that this is a WAM call
RequestParameterBuilder.addNativeBroker(parameters);

// instrument JS-platform bridge specific fields
performanceClient.addFields(
{
isPlatformAuthorizeRequest: true,
},
request.correlationId
);

// pass the req_cnf for POP
if (
request.authenticationScheme === Constants.AuthenticationScheme.POP
Expand Down
68 changes: 67 additions & 1 deletion lib/msal-browser/test/app/PublicClientApplication.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ import { PlatformAuthDOMHandler } from "../../src/broker/nativeBroker/PlatformAu
import * as BrowserRootPerformanceEvents from "../../src/telemetry/BrowserRootPerformanceEvents.js";
import * as CacheKeys from "../../src/cache/CacheKeys.js";
import { getAccountKeysCacheKey } from "../../src/cache/CacheKeys.js";
import exp from "constants";

const cacheConfig = {
cacheLocation: BrowserCacheLocation.SessionStorage,
Expand Down Expand Up @@ -1570,14 +1571,25 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
done();
});
});
it("goes directly to the native broker if nativeAccountId is present", async () => {
it("goes directly to the platform broker if nativeAccountId is present and emits platform telemetry", async () => {
const config = {
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
},
system: {
allowPlatformBroker: true,
},
telemetry: {
client: new BrowserPerformanceClient({
auth: {
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
},
}),
application: {
appName: TEST_CONFIG.applicationName,
appVersion: TEST_CONFIG.applicationVersion,
},
},
};
pca = new PublicClientApplication(config);

Expand All @@ -1588,6 +1600,21 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
pca = (pca as any).controller;

const testAccount = BASIC_NATIVE_TEST_ACCOUNT_INFO;
const testTokenResponse: AuthenticationResult = {
authority: TEST_CONFIG.validAuthority,
uniqueId: testAccount.localAccountId,
tenantId: testAccount.tenantId,
scopes: TEST_CONFIG.DEFAULT_SCOPES,
idToken: "test-idToken",
idTokenClaims: {},
accessToken: "test-accessToken",
fromCache: false,
correlationId: RANDOM_TEST_GUID,
expiresOn: TestTimeUtils.nowDateWithOffset(3600),
account: testAccount,
tokenType: Constants.AuthenticationScheme.BEARER,
fromNativeBroker: true,
};

jest.spyOn(BrowserCrypto, "createNewGuid").mockReturnValue(
RANDOM_TEST_GUID
Expand All @@ -1599,14 +1626,53 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
"acquireTokenRedirect"
)
.mockResolvedValue();

const nativeHandleRedirectPromiseSpy = jest
.spyOn(StandardController.prototype, "handleRedirectPromise")
.mockImplementation(
async (req: any, measurementName?: string) => {
// Find existing performance event by correlationId and modify it
const performanceClient =
pca.getConfiguration().telemetry?.client;
if (performanceClient && measurementName) {
const events = (performanceClient as any)
.eventsByCorrelationId;
const existingEvent = events.get(req.correlationId);
if (existingEvent) {
existingEvent.isNativeBroker = true;
}
}
return testTokenResponse;
}
);

const redirectSpy: jest.SpyInstance = jest
.spyOn(RedirectClient.prototype, "acquireToken")
.mockResolvedValue();

// Mock the acquireTokensFromCache method to simulate a cache miss (rejects first), then WAM success (resolves)
jest.spyOn(
PlatformAuthInteractionClient.prototype as any,
"acquireTokensFromCache"
)
// First call rejects (simulates cache miss), second call resolves (simulates WAM success)
.mockRejectedValueOnce(new Error("No cached tokens"))
.mockResolvedValue(testTokenResponse);

// Add performance callback
const callbackId = pca.addPerformanceCallback((events) => {
expect(events[0].isNativeBroker).toBe(true);
expect(events[0].isPlatformBrokerRequest).toBe(true);
pca.removePerformanceCallback(callbackId);
});

await pca.acquireTokenRedirect({
scopes: ["User.Read"],
account: testAccount,
});

const response = pca.handleRedirectPromise();

expect(nativeAcquireTokenSpy).toHaveBeenCalledTimes(1);
expect(redirectSpy).toHaveBeenCalledTimes(0);
});
Expand Down
Loading
Loading