Skip to content

Commit ff7f72b

Browse files
fadidurahCopilot
andcommitted
Wire ClientDataInfo through AcquireTokenResult and BaseException
Propagate ClientDataInfo (parsed from the x-ms-clientdata token response header and the clientdata authorize redirect query parameter) through AcquireTokenResult on success paths and through BaseException on failure paths. Includes broker IPC serialization via BrokerResult so the payload reaches the MSAL caller process for both success and error responses. Adds a raw field to ClientDataInfo preserving the original pipe-delimited string for partner teams that want the unparsed payload. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 16b1bc8 commit ff7f72b

15 files changed

Lines changed: 254 additions & 8 deletions

File tree

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
vNext
22
----------
3+
- [PATCH] Wire ClientDataInfo (server telemetry from x-ms-clientdata response header and clientdata authorize parameter) through AcquireTokenResult on success and through BaseException on failure (including across the broker IPC boundary), and preserve the original raw pipe-delimited payload on ClientDataInfo for partner teams (#3109)
34
- [PATCH] Extend filter-then-clone optimization to load() and getIdTokensForAccountRecord() in MsalOAuth2TokenCache: when ENABLE_FILTER_THEN_CLONE_IN_MEMORY_CACHE flight is enabled, skip clone-all preload and call direct flight-gated overloads that clone only matching credentials; add new getCredentialsFilteredBy overload with kid support (#3100)
45
- [PATCH] Move Multiple Listening apps check to the authorization layer (#3070)
56
- [PATCH] Edge TB: Fix lookup mode (#3108)

common/src/main/java/com/microsoft/identity/common/internal/broker/BrokerResult.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ private static class SerializedNames {
9797
static final String SPE_RING = "spe_ring";
9898
static final String CLI_TELEM_ERRORCODE = "cli_telem_error_code";
9999
static final String CLI_TELEM_SUB_ERROR_CODE = "cli_telem_suberror_code";
100+
static final String CLIENT_DATA_INFO = "client_data_info";
100101
}
101102

102103
private static final long serialVersionUID = 8606631820514878489L;
@@ -236,6 +237,13 @@ private static class SerializedNames {
236237
@SerializedName(SerializedNames.REFRESH_TOKEN_AGE)
237238
private String mRefreshTokenAge;
238239

240+
/**
241+
* Server client data info from x-ms-clientdata response header (pipe-delimited format).
242+
*/
243+
@Nullable
244+
@SerializedName(SerializedNames.CLIENT_DATA_INFO)
245+
private String mClientDataInfoRaw;
246+
239247
/**
240248
* Boolean to indicate if the request succeeded without exceptions.
241249
*/
@@ -347,6 +355,7 @@ private BrokerResult(@NonNull final Builder builder) {
347355
mCachedAt = builder.mCachedAt;
348356
mSpeRing = builder.mSpeRing;
349357
mRefreshTokenAge = builder.mRefreshTokenAge;
358+
mClientDataInfoRaw = builder.mClientDataInfoRaw;
350359
mSuccess = builder.mSuccess;
351360
mTenantProfileData = builder.mTenantProfileData;
352361
mServicedFromCache = builder.mServicedFromCache;
@@ -426,6 +435,11 @@ public String getSpeRing() {
426435
return mSpeRing;
427436
}
428437

438+
@Nullable
439+
public String getClientDataInfoRaw() {
440+
return mClientDataInfoRaw;
441+
}
442+
429443
public long getCachedAt() {
430444
return mCachedAt;
431445
}
@@ -518,6 +532,7 @@ public static class Builder {
518532
private long mCachedAt;
519533
private String mSpeRing;
520534
private String mRefreshTokenAge;
535+
private String mClientDataInfoRaw;
521536
private boolean mSuccess;
522537
private String mNegotiatedBrokerProtocolVersion;
523538
private List<ICacheRecord> mTenantProfileData;
@@ -631,6 +646,11 @@ public Builder refreshTokenAge(final String refreshTokenAge) {
631646
return this;
632647
}
633648

649+
public Builder clientDataInfoRaw(@Nullable final String clientDataInfoRaw) {
650+
this.mClientDataInfoRaw = clientDataInfoRaw;
651+
return this;
652+
}
653+
634654
public Builder success(boolean success) {
635655
this.mSuccess = success;
636656
return this;

common/src/main/java/com/microsoft/identity/common/internal/controllers/LocalMSALController.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import com.microsoft.identity.common.java.providers.RawAuthorizationResult;
6363
import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsAuthorizationRequest;
6464
import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsAuthorizationResponse;
65+
import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsAuthorizationResult;
6566
import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsTokenRequest;
6667
import com.microsoft.identity.common.java.providers.oauth2.AuthorizationRequest;
6768
import com.microsoft.identity.common.java.providers.oauth2.AuthorizationResult;
@@ -168,6 +169,13 @@ public AcquireTokenResult acquireToken(
168169
);
169170
acquireTokenResult.setAuthorizationResult(result);
170171

172+
// Wire ClientDataInfo from the authorization result (authorize endpoint).
173+
if (result instanceof MicrosoftStsAuthorizationResult) {
174+
acquireTokenResult.setClientDataInfo(
175+
((MicrosoftStsAuthorizationResult) result).getClientDataInfo()
176+
);
177+
}
178+
171179
ResultUtil.logResult(TAG, result);
172180

173181
if (result.getAuthorizationStatus().equals(AuthorizationStatus.SUCCESS)) {
@@ -181,6 +189,11 @@ public AcquireTokenResult acquireToken(
181189

182190
acquireTokenResult.setTokenResult(tokenResult);
183191

192+
// Prefer ClientDataInfo from the token endpoint (later, more authoritative call).
193+
if (tokenResult != null && tokenResult.getClientDataInfo() != null) {
194+
acquireTokenResult.setClientDataInfo(tokenResult.getClientDataInfo());
195+
}
196+
184197
if (tokenResult != null && tokenResult.getSuccess()) {
185198
//4) Save tokens in token cache
186199
final List<ICacheRecord> records = saveTokens(
@@ -205,6 +218,11 @@ public AcquireTokenResult acquireToken(
205218
false
206219
)
207220
);
221+
222+
// Set ClientDataInfo on the LocalAuthenticationResult for IPC propagation
223+
final LocalAuthenticationResult localResult =
224+
(LocalAuthenticationResult) acquireTokenResult.getLocalAuthenticationResult();
225+
localResult.setClientDataInfo(acquireTokenResult.getClientDataInfo());
208226
}
209227
}
210228

@@ -742,6 +760,9 @@ public AcquireTokenResult acquireDeviceCodeFlowToken(
742760

743761
// Assign token result
744762
acquireTokenResult.setTokenResult(tokenResult);
763+
if (tokenResult != null) {
764+
acquireTokenResult.setClientDataInfo(tokenResult.getClientDataInfo());
765+
}
745766

746767
// If the token is valid, save it into token cache
747768
final List<ICacheRecord> records = saveTokens(
@@ -764,6 +785,13 @@ public AcquireTokenResult acquireDeviceCodeFlowToken(
764785
false
765786
)
766787
);
788+
789+
// Set ClientDataInfo on the LocalAuthenticationResult for IPC propagation
790+
if (tokenResult != null) {
791+
final LocalAuthenticationResult localResult =
792+
(LocalAuthenticationResult) acquireTokenResult.getLocalAuthenticationResult();
793+
localResult.setClientDataInfo(tokenResult.getClientDataInfo());
794+
}
767795
} catch (Exception error) {
768796
Telemetry.emit(
769797
new ApiEndEvent()

common/src/main/java/com/microsoft/identity/common/internal/result/MsalBrokerResultAdapter.java

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
import com.microsoft.identity.common.java.result.GenerateShrResult;
9999
import com.microsoft.identity.common.java.result.ILocalAuthenticationResult;
100100
import com.microsoft.identity.common.java.result.LocalAuthenticationResult;
101+
import com.microsoft.identity.common.java.telemetry.ClientDataInfo;
101102
import com.microsoft.identity.common.java.ui.PreferredAuthMethod;
102103
import com.microsoft.identity.common.java.util.BrokerProtocolVersionUtil;
103104
import com.microsoft.identity.common.java.util.HeaderSerializationUtil;
@@ -284,6 +285,17 @@ public Bundle bundleFromAuthenticationResultForWebApps(@NonNull final ILocalAuth
284285
.success(true)
285286
.servicedFromCache(authenticationResult.isServicedFromCache());
286287

288+
// Serialize ClientDataInfo as raw pipe-delimited string for IPC transfer.
289+
// The raw field is populated by ClientDataInfo.fromPipeDelimited(), the only
290+
// path that populates the parsed fields, so it is safe to ship as-is.
291+
if (authenticationResult instanceof LocalAuthenticationResult) {
292+
final ClientDataInfo clientDataInfo =
293+
((LocalAuthenticationResult) authenticationResult).getClientDataInfo();
294+
if (clientDataInfo != null) {
295+
brokerResultBuilder.clientDataInfoRaw(clientDataInfo.getRaw());
296+
}
297+
}
298+
287299
if (shouldRemoveRefreshTokenFromResult(authenticationResult, negotiatedBrokerProtocolVersion)){
288300
brokerResultBuilder.tenantProfileRecords(
289301
removeRefreshTokenFromCacheRecords(
@@ -411,6 +423,12 @@ public Bundle bundleFromBaseException(@NonNull final BaseException exception,
411423
.speRing(exception.getSpeRing())
412424
.refreshTokenAge(exception.getRefreshTokenAge());
413425

426+
// Serialize ClientDataInfo (server telemetry from x-ms-clientdata) so it
427+
// survives the broker IPC boundary on error paths.
428+
if (exception.getClientDataInfo() != null) {
429+
builder.clientDataInfoRaw(exception.getClientDataInfo().getRaw());
430+
}
431+
414432
if (exception instanceof ServiceException) {
415433
final ServiceException serviceException = (ServiceException) exception;
416434
builder.subErrorCode(serviceException.getSubErrorCode())
@@ -476,12 +494,21 @@ public ILocalAuthenticationResult authenticationResultFromBundle(@NonNull final
476494
throw new ClientException(INVALID_BROKER_BUNDLE, "getTenantProfileData is null.");
477495
}
478496

479-
return new LocalAuthenticationResult(
497+
final LocalAuthenticationResult localAuthResult = new LocalAuthenticationResult(
480498
tenantProfileCacheRecords.get(0),
481499
tenantProfileCacheRecords,
482500
SdkType.MSAL,
483501
brokerResult.isServicedFromCache()
484502
);
503+
504+
// Deserialize ClientDataInfo from the broker result if available
505+
final ClientDataInfo clientDataInfo =
506+
ClientDataInfo.fromPipeDelimited(brokerResult.getClientDataInfoRaw());
507+
if (clientDataInfo != null) {
508+
localAuthResult.setClientDataInfo(clientDataInfo);
509+
}
510+
511+
return localAuthResult;
485512
}
486513

487514
@NonNull
@@ -516,6 +543,14 @@ public BaseException getBaseExceptionFromBundle(@NonNull final Bundle resultBund
516543
baseException.setBrokerPerformanceMetrics(metrics);
517544
}
518545

546+
// Restore ClientDataInfo (server telemetry) from the broker result so callers
547+
// catching the exception can inspect server-side error context.
548+
if (!StringUtil.isNullOrEmpty(brokerResult.getClientDataInfoRaw())) {
549+
baseException.setClientDataInfo(
550+
ClientDataInfo.fromPipeDelimited(brokerResult.getClientDataInfoRaw())
551+
);
552+
}
553+
519554
// Set broker app info if available
520555
if (resultBundle.containsKey(AuthenticationConstants.Broker.BROKER_VERSION)) {
521556
baseException.setBrokerAppVersion(
@@ -994,7 +1029,16 @@ public AcquireTokenResult getDeviceCodeFlowTokenResultFromResultBundle(@NonNull
9941029

9951030
if (resultBundle.getBoolean(AuthenticationConstants.Broker.BROKER_REQUEST_V2_SUCCESS)) {
9961031
final AcquireTokenResult acquireTokenResult = new AcquireTokenResult();
997-
acquireTokenResult.setLocalAuthenticationResult(authenticationResultFromBundle(resultBundle));
1032+
final ILocalAuthenticationResult authResult = authenticationResultFromBundle(resultBundle);
1033+
acquireTokenResult.setLocalAuthenticationResult(authResult);
1034+
1035+
// Propagate ClientDataInfo from LocalAuthenticationResult to AcquireTokenResult
1036+
if (authResult instanceof LocalAuthenticationResult) {
1037+
acquireTokenResult.setClientDataInfo(
1038+
((LocalAuthenticationResult) authResult).getClientDataInfo()
1039+
);
1040+
}
1041+
9981042
span.setStatus(StatusCode.OK);
9991043
return acquireTokenResult;
10001044
} else if (brokerResult.getErrorCode().equals(ErrorStrings.DEVICE_CODE_FLOW_AUTHORIZATION_PENDING_ERROR_CODE)) {
@@ -1018,9 +1062,16 @@ AcquireTokenResult getAcquireTokenResultFromResultBundle(@NonNull final Bundle r
10181062
final MsalBrokerResultAdapter resultAdapter = new MsalBrokerResultAdapter();
10191063
if (resultBundle.getBoolean(AuthenticationConstants.Broker.BROKER_REQUEST_V2_SUCCESS)) {
10201064
final AcquireTokenResult acquireTokenResult = new AcquireTokenResult();
1021-
acquireTokenResult.setLocalAuthenticationResult(
1022-
resultAdapter.authenticationResultFromBundle(resultBundle)
1023-
);
1065+
final ILocalAuthenticationResult authResult =
1066+
resultAdapter.authenticationResultFromBundle(resultBundle);
1067+
acquireTokenResult.setLocalAuthenticationResult(authResult);
1068+
1069+
// Propagate ClientDataInfo from LocalAuthenticationResult to AcquireTokenResult
1070+
if (authResult instanceof LocalAuthenticationResult) {
1071+
acquireTokenResult.setClientDataInfo(
1072+
((LocalAuthenticationResult) authResult).getClientDataInfo()
1073+
);
1074+
}
10241075
// Set broker performance metrics if available
10251076
final BrokerPerformanceMetrics metrics = resultAdapter.getBrokerPerformanceMetricsFromBundle(resultBundle);
10261077
if (metrics != null) {

common4j/src/main/com/microsoft/identity/common/java/controllers/BaseController.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ public AcquireTokenResult acquireTokenWithPassword(@NonNull final RopcTokenComma
219219
final TokenResult tokenResult = oAuth2Strategy.requestToken(ropcTokenRequest);
220220

221221
acquireTokenResult.setTokenResult(tokenResult);
222+
if (tokenResult != null) {
223+
acquireTokenResult.setClientDataInfo(tokenResult.getClientDataInfo());
224+
}
222225

223226
@SuppressWarnings(WarningType.rawtype_warning) final OAuth2TokenCache tokenCache = parameters.getOAuth2TokenCache();
224227

@@ -251,6 +254,9 @@ public AcquireTokenResult acquireTokenWithPassword(@NonNull final RopcTokenComma
251254
Telemetry.emit(new CacheEndEvent());
252255
}
253256

257+
// Set server client data info on the authentication result for IPC propagation
258+
authenticationResult.setClientDataInfo(tokenResult.getClientDataInfo());
259+
254260
// Set the AuthenticationResult on the final result object
255261
acquireTokenResult.setLocalAuthenticationResult(authenticationResult);
256262
}
@@ -484,6 +490,7 @@ protected void renewAccessToken(@NonNull final SilentTokenCommandParameters para
484490
);
485491

486492
acquireTokenSilentResult.setTokenResult(tokenResult);
493+
acquireTokenSilentResult.setClientDataInfo(tokenResult.getClientDataInfo());
487494

488495
ResultUtil.logResult(methodTag, tokenResult);
489496

@@ -528,6 +535,9 @@ protected void renewAccessToken(@NonNull final SilentTokenCommandParameters para
528535
Telemetry.emit(new CacheEndEvent());
529536
}
530537

538+
// Set server client data info on the authentication result for IPC propagation
539+
authenticationResult.setClientDataInfo(tokenResult.getClientDataInfo());
540+
531541
// Set the AuthenticationResult on the final result object
532542
acquireTokenSilentResult.setLocalAuthenticationResult(authenticationResult);
533543
} else {

common4j/src/main/com/microsoft/identity/common/java/controllers/ExceptionAdapter.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import com.microsoft.identity.common.java.opentelemetry.AttributeName;
4545
import com.microsoft.identity.common.java.opentelemetry.SpanExtension;
4646
import com.microsoft.identity.common.java.providers.microsoft.MicrosoftAuthorizationErrorResponse;
47+
import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsAuthorizationResult;
4748
import com.microsoft.identity.common.java.providers.oauth2.AuthorizationErrorResponse;
4849
import com.microsoft.identity.common.java.providers.oauth2.AuthorizationResult;
4950
import com.microsoft.identity.common.java.providers.oauth2.TokenErrorResponse;
@@ -84,7 +85,15 @@ public static BaseException exceptionFromAcquireTokenResult(final AcquireTokenRe
8485

8586
if (null != authorizationResult) {
8687
if (!authorizationResult.getSuccess()) {
87-
return exceptionFromAuthorizationResult(authorizationResult, commandParameters);
88+
final BaseException authException = exceptionFromAuthorizationResult(authorizationResult, commandParameters);
89+
// Attach ClientDataInfo from the authorize redirect (clientdata query param)
90+
// so callers can inspect server-side error context on auth failures.
91+
if (authorizationResult instanceof MicrosoftStsAuthorizationResult) {
92+
authException.setClientDataInfo(
93+
((MicrosoftStsAuthorizationResult) authorizationResult).getClientDataInfo()
94+
);
95+
}
96+
return authException;
8897
}
8998
} else {
9099
Logger.warn(
@@ -183,6 +192,7 @@ public static ServiceException exceptionFromTokenResult(final TokenResult tokenR
183192

184193
outErr = getExceptionFromTokenErrorResponse(commandParameters, tokenResult.getErrorResponse());
185194
applyCliTelemInfo(tokenResult.getCliTelemInfo(), outErr);
195+
outErr.setClientDataInfo(tokenResult.getClientDataInfo());
186196
} else {
187197
Logger.warn(
188198
TAG + methodName,

common4j/src/main/com/microsoft/identity/common/java/exception/BaseException.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
package com.microsoft.identity.common.java.exception;
2424

2525
import com.microsoft.identity.common.java.broker.IBrokerInfoProvider;
26+
import com.microsoft.identity.common.java.telemetry.ClientDataInfo;
2627
import com.microsoft.identity.common.java.telemetry.ITelemetryAccessor;
2728
import com.microsoft.identity.common.java.telemetry.Telemetry;
2829
import com.microsoft.identity.common.java.telemetry.events.ErrorEvent;
@@ -70,6 +71,14 @@ public class BaseException extends Exception implements IErrorInformation, ITele
7071
@Nullable
7172
private String mCliTelemSubErrorCode;
7273

74+
/**
75+
* Server-side telemetry from the x-ms-clientdata response header (/token error responses)
76+
* or the clientdata query parameter (/authorize error redirects). Useful for diagnosing
77+
* server-driven failures (account_type, error, sub_error, caller_data_boundary, cloud_instance).
78+
*/
79+
@Nullable
80+
private ClientDataInfo mClientDataInfo;
81+
7382
private String mErrorCode;
7483

7584
private String mSubErrorCode;
@@ -213,6 +222,15 @@ public void setCliTelemSubErrorCode(@Nullable final String cliTelemSubErrorCode)
213222
this.mCliTelemSubErrorCode = cliTelemSubErrorCode;
214223
}
215224

225+
@Nullable
226+
public ClientDataInfo getClientDataInfo() {
227+
return mClientDataInfo;
228+
}
229+
230+
public void setClientDataInfo(@Nullable final ClientDataInfo clientDataInfo) {
231+
this.mClientDataInfo = clientDataInfo;
232+
}
233+
216234
@Nullable
217235
public String getCorrelationId() {
218236
return mCorrelationId;

common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/AbstractMicrosoftStsTokenResponseHandler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public TokenResult handleTokenResponse(@NonNull final HttpResponse response) thr
113113
final ClientDataInfo clientDataInfo = ClientDataInfo.fromPipeDelimited(clientDataHeader);
114114
if (null != clientDataInfo) {
115115
clientDataInfo.emitToSpan();
116+
result.setClientDataInfo(clientDataInfo);
116117
}
117118
}
118119

0 commit comments

Comments
 (0)