Add claimsFromClient API for client-originated claims#1039
Open
Robbie-Microsoft wants to merge 9 commits into
Open
Add claimsFromClient API for client-originated claims#1039Robbie-Microsoft wants to merge 9 commits into
Robbie-Microsoft wants to merge 9 commits into
Conversation
Ports msal-dotnet PR #5999 (WithClaimsFromClient) to msal-java as a new per-request builder method claimsFromClient(String) on the Managed Identity, Client Credentials, On-Behalf-Of, and User Federated Identity Credential parameter builders. Unlike server-issued `claims` challenges (CAE), which bypass and refresh the cache, client-originated claims are forwarded on the wire as a standard OAuth `claims` parameter and are cached, keyed per distinct claims value via an extended access-token cache key (extCacheKeyHash). - New claimsFromClient(String) builders: blank is a no-op; non-object JSON throws MsalClientException(INVALID_JSON) via JsonHelper.validateJsonObjectFormat. - Wire injection in TokenRequestExecutor for confidential-client flows; IMDS transport (query string for GET, body for POST) in AbstractManagedIdentitySource. - Managed Identity restricts client claims to IMDS (MSIv1), allowing only the xms_az_nwperimid claim; other sources or keys throw INVALID_REQUEST. - Cache write/read paths keyed on extCacheKeyHash across CC/MI/OBO/FIC, including user-token isolation for the FIC flow. - Tests: new ClientClaimsTest plus FIC and Managed Identity coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a new per-request claimsFromClient(String claimsJson) API to multiple MSAL4J flows (Client Credentials, OBO, Managed Identity, and User Federated Identity Credential) to support client-originated OAuth claims that are forwarded on the wire and cached with isolation via an extended cache-key hash.
Changes:
- Adds
claimsFromClient(...)builder support andclientClaims()/computeExtCacheKeyHash()plumbing across the four parameter types. - Merges client-originated claims into the outgoing OAuth token request
claimsparameter for confidential-client flows, and forwards claims to IMDS managed identity with MSIv1 key restrictions. - Extends TokenCache read/write paths to incorporate
extCacheKeyHashso distinct client-claims values produce distinct cache entries, with new/updated tests to validate wire + cache behavior.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/UserFederatedIdentityCredentialTest.java | Adds user-FIC wire forwarding + cache-isolation tests for client claims. |
| msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ManagedIdentityTests.java | Adds managed-identity IMDS forwarding, cache isolation, and invalid-usage tests for client claims. |
| msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientClaimsTest.java | New test suite for JSON validation, builder behavior, wire forwarding, and cache isolation (CC/OBO). |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/UserFederatedIdentityCredentialParameters.java | Adds client-claims storage + extended cache-key hash computation for user-FIC parameters. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java | Merges client-originated claims into the outgoing OAuth claims request parameter for confidential-client flows. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java | Generalizes ext cache-key hash computation and filters cache reads by extCacheKeyHash. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OnBehalfOfParameters.java | Adds client-claims + extended cache-key hash computation for OBO parameters. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ManagedIdentityParameters.java | Adds client-claims + extended cache-key hash computation for managed identity parameters. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/JsonHelper.java | Adds JSON-object validation helper for claimsFromClient inputs. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IAcquireTokenParameters.java | Adds default clientClaims() and computeExtCacheKeyHash() hooks to unify behavior across flows. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientCredentialParameters.java | Adds client-claims support and folds it into the extended cache-key hash for client credentials. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java | Introduces INVALID_REQUEST for unsupported parameter combinations in this feature. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenSilentSupplier.java | Passes extCacheKeyHash into cache lookups so isolation works end-to-end. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByUserFederatedIdentityCredentialSupplier.java | Propagates extCacheKeyHash into the silent request path for user-FIC. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByOnBehalfOfSupplier.java | Propagates extCacheKeyHash into the silent request path for OBO. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByManagedIdentitySupplier.java | Propagates extCacheKeyHash into the silent request path for managed identity. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AbstractManagedIdentitySource.java | Forwards client claims to IMDS and enforces MSIv1 xms_az_nwperimid top-level key restriction. |
Extends client-originated claims coverage to the last remaining confidential-client flow (Authorization Code / web-app code redemption), matching the generic AbstractConfidentialClientAcquireTokenParameterBuilder extension in msal-dotnet PR #5999. AuthorizationCodeParameters now carries clientClaims with the same cache-key isolation pattern as OnBehalfOfParameters (extCacheKeyHash). Wire forwarding and cache writes are already generic, so the claims are forwarded as the OAuth claims body parameter and cached per distinct value. Adds an auth-code wire test plus auth-code cases to the builder no-op and invalid-JSON tests in ClientClaimsTest. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- JsonHelper.validateJsonObjectFormat: reject trailing tokens after the
root object (e.g. "{}{}", "{} garbage") by requiring END_DOCUMENT, and
stop including the parser message in the exception so a (potentially
sensitive) claims payload is never leaked. Uses a single constant message.
- TokenRequestExecutor: correct the now-outdated comment listing the flows
covered by the client-claims wire merge (adds authorization-code, which is
shared with PublicClientApplication).
- ClientCredentialParameters: remove an accidentally duplicated Javadoc block
before computeExtCacheKeyHash().
- ClientClaimsTest: add trailing-token rejection and payload-not-leaked tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avery-Dunn
reviewed
Jun 29, 2026
Avery-Dunn
reviewed
Jun 29, 2026
Avery-Dunn
reviewed
Jun 29, 2026
- ClientClaimsTest: add a test proving server claims() and client claimsFromClient() merge into a single wire "claims" param, plus a precedence test (client value wins on a leaf conflict, nested objects deep-merge). Consolidate the three JSON-validation tests and the trailing-token test into one, keeping the payload-not-leaked assertion, to focus the suite on library behavior rather than parser mechanics. - AuthorizationCodeParameters.claimsFromClient: document the public-client caveat (a public-client auth-code token cached under the extended key is not returned by acquireTokenSilently, which refreshes without claims). - TokenCache.computeExtCacheKeyHashForRequest: include authorization-code in the covered-flows Javadoc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply the applicable review findings from the sibling MSAL PRs (go #629, python #937, js #8686) that port dotnet #5999: - Managed Identity: drop the MSIv1 `xms_az_nwperimid` allow-list. MSAL no longer inspects/restricts claim keys; the JSON object is forwarded to IMDS as-is and IMDS decides what it accepts. Matches the final .NET and Go state. - Validate the MI source (IMDS-only) *before* the cache read in AcquireTokenByManagedIdentitySupplier, not just on the transport path, so an unsupported source can never return a cached token or reach the wire. The shared validateClientClaimsSource helper keeps a single rule definition (kept at the transport layer as defense-in-depth). - Docs: note that callers must send the identical claims value on every request for a given token, since the raw value is part of the cache key (changing/omitting it routes to a different cache partition). Applied to all confidential-client builders and Managed Identity. Cache-key hash format is intentionally left unchanged: Java matches the merged .NET reference byte-for-byte (pinned by FmiTest.fmiPath_HashValueMatchesCrossSDK). Go's length-prefixed variant is the cross-SDK outlier; changing Java would break .NET parity and the golden test, so it needs .NET-led coordination. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
905315a removed the MI MSIv1 claim-key allow-list, so INVALID_REQUEST is no longer thrown for an unsupported claim key — only for an unsupported managed identity source. Drop the stale clause from the error-code Javadoc. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A RefreshTokenRequest inherits the parent silent request's RequestContext, whose apiParameters is a SilentParameters that carries no client-originated claims (computeExtCacheKeyHash() returns ""). As a result, when an access token acquired with an extended cache key was refreshed, the refreshed token was written back to the default cache partition, breaking isolation. This is reachable for User-FIC: its supplier builds an account-scoped silent request and threads the ext-cache-key hash onto it, and User-FIC tokens carry a refresh token, so an expired token hits the refresh path. (CC/MI/OBO build account-less silent requests and never reach the refresh path.) Fix: in TokenCache.computeExtCacheKeyHashForRequest, prefer the parent silent request's ext hash for a RefreshTokenRequest (exposed via a new RefreshTokenRequest.extCacheKeyHash() accessor) so the refreshed token stays in the same partition. Adds a regression test asserting a refreshed User-FIC token retains its client-claims partition (fails before the fix with two entries). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The claimsFromClient Javadoc framed the silent-refresh cache caveat as a public-client concern that "matters less" on confidential clients. In fact acquireTokenSilently uses SilentParameters (no claimsFromClient surface) on both PublicClientApplication and ConfidentialClientApplication, so a silent refresh won't match the client-claims cache entry and refreshes without the claims in either case. Reword to state the caveat plainly for both types. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Avery-Dunn
approved these changes
Jul 1, 2026
Restrict the per-request claimsFromClient API to confidential-client flows (client credentials, on-behalf-of, user-FIC, authorization code), matching .NET where WithClaimsFromClient is confidential-only. - Revert all managed-identity claims plumbing in ManagedIdentityParameters, AbstractManagedIdentitySource, and AcquireTokenByManagedIdentitySupplier. - Remove the now-orphaned INVALID_REQUEST error code (added only for the MI source validation). - Remove the ManagedIdentityTests ClientClaimsTests nested class and the managed-identity cases in ClientClaimsTest. Shared cache/wire infrastructure and the four confidential parameter classes are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a per-request
claimsFromClient(String claimsJson)builder method for client-originated claims (ports msal-dotnet #5999). Confidential-client flows only: Client Credentials, On-Behalf-Of, User Federated Identity Credential, and Authorization Code.Unlike server-issued
claimschallenges (which bypass the cache), client claims are forwarded on the wire as a standard OAuthclaimsparameter and cached, keyed per distinct value via an extended cache-key hash. Blank/whitespace is a no-op; invalid JSON throwsMsalClientException(INVALID_JSON)without echoing the payload.Send the identical value on every request for a given token — the raw value is part of the cache key.