Skip to content

Add claimsFromClient API for client-originated claims#1039

Open
Robbie-Microsoft wants to merge 9 commits into
devfrom
rginsburg/client_claims
Open

Add claimsFromClient API for client-originated claims#1039
Robbie-Microsoft wants to merge 9 commits into
devfrom
rginsburg/client_claims

Conversation

@Robbie-Microsoft

@Robbie-Microsoft Robbie-Microsoft commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

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 claims challenges (which bypass the cache), client claims are forwarded on the wire as a standard OAuth claims parameter and cached, keyed per distinct value via an extended cache-key hash. Blank/whitespace is a no-op; invalid JSON throws MsalClientException(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.

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 and clientClaims()/computeExtCacheKeyHash() plumbing across the four parameter types.
  • Merges client-originated claims into the outgoing OAuth token request claims parameter for confidential-client flows, and forwards claims to IMDS managed identity with MSIv1 key restrictions.
  • Extends TokenCache read/write paths to incorporate extCacheKeyHash so 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.

Comment thread msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/JsonHelper.java
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/JsonHelper.java
Comment thread msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java Outdated
Comment thread msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientCredentialParameters.java Outdated
- 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>
Comment thread msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientClaimsTest.java Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Comment thread msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java Outdated
- 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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Comment thread msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Comment thread msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java Outdated
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants