Add client_claims support to confidential client and managed identity flows#937
Open
Robbie-Microsoft wants to merge 3 commits into
Open
Add client_claims support to confidential client and managed identity flows#937Robbie-Microsoft wants to merge 3 commits into
Robbie-Microsoft wants to merge 3 commits into
Conversation
… flows Port of msal-dotnet PR 5999 (WithClaimsFromClient). Adds a `client_claims` keyword argument to `ConfidentialClientApplication.acquire_token_for_client` and `ManagedIdentityClient.acquire_token_for_client` for forwarding client-originated claims (e.g. the network security perimeter `xms_az_nwperimid` claim) to ESTS/IMDS. Unlike `claims_challenge` (server-issued, bypasses the cache), `client_claims` tokens are cached and the cache entry is keyed on the claims value, reusing the existing `_compute_ext_cache_key` mechanism (the `fmi_path` precedent). - token_cache: add `_parse_claims_or_raise`, `_deep_merge_dict`, `_merge_claims` helpers; `claims` stays excluded from the ext cache key while `client_claims` participates in it. - oauth2: strip the cache-key-only `client_claims` pseudo-parameter from the wire body while preserving it for the cache-add event. - application: validate `client_claims`, merge it into the OAuth `claims` body parameter, and isolate the cache by claims value. - managed_identity: support `client_claims` on the IMDS (Azure VM) source only (sent as the `claims` query parameter); other sources raise; MSIv1 restricts the claims JSON to only the `xms_az_nwperimid` key. Adds unit tests covering cache isolation, wire shape, claim merging, source restrictions, and MSIv1 validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds support for forwarding client-originated claims via a new client_claims argument in confidential-client and managed-identity client-credentials flows, while ensuring tokens remain cacheable and isolated by the claims value (distinct from server-issued claims_challenge behavior).
Changes:
- Introduces shared claims parsing/merging helpers and uses
client_claimsto participate in the extended cache key (while keepingclaimsexcluded). - Ensures
client_claimsis merged into the OAuthclaimsparameter but stripped from the actual HTTP request body (cache-key-only pseudo-parameter). - Adds managed identity support for
client_claimson IMDS/Azure VM only (including MSIv1 validation), with unit tests covering caching and wire-shape behaviors.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
msal/token_cache.py |
Adds claims helpers and leverages existing ext-cache-key mechanism to isolate cached tokens by client_claims. |
msal/oauth2cli/oauth2.py |
Prevents client_claims from being sent on the wire while preserving it for cache add events. |
msal/application.py |
Adds client_claims to CCA acquire-token-for-client flow, validates/merges claims, and isolates cache entries by claims value. |
msal/managed_identity.py |
Adds client_claims support for IMDS/Azure VM only, isolates cache by claims, and validates MSIv1 claim constraints. |
tests/test_token_cache.py |
Adds unit tests for cache-key isolation and claims helper behavior. |
tests/test_mi.py |
Adds managed-identity unit tests for IMDS forwarding, cache isolation, and unsupported source errors. |
tests/test_application.py |
Adds CCA unit tests for wire shape, merging behavior, and cache isolation with client_claims. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… code, silent) Phase 1 added client_claims to acquire_token_for_client. This extends it to the remaining confidential client flows so client-originated claims are forwarded and cache-isolated consistently, mirroring msal-dotnet PR 5999's WithClaimsFromClient (which applies to all confidential client builders): - acquire_token_on_behalf_of (OBO) - acquire_token_by_user_federated_identity_credential (FIC) - acquire_token_by_authorization_code - acquire_token_silent / acquire_token_silent_with_error (cache-read isolation plus refresh-token request merge) A shared _stash_client_claims() helper validates the value and stashes it into the request data, so it (a) contributes to the extended cache key and (b) is merged into the OAuth "claims" parameter while being stripped from the wire body. Adds unit tests for each flow (wire shape, validation, cache isolation). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- token_cache._parse_claims_or_raise now also catches TypeError (raised when the input is not a str/bytes) and surfaces the same friendly ValueError, so every caller behaves consistently regardless of the bad input's type. - ManagedIdentityClient.acquire_token_for_client now rejects non-string client_claims with a ValueError (mirroring the confidential-client flows), preventing a raw TypeError leak and inconsistent extended-cache-key hashing. - ConfidentialClientApplication.acquire_token_for_client now reuses the shared _stash_client_claims() helper instead of duplicating the validate-and-stash logic, removing the risk of the two paths diverging. - Add cross-referencing docstring notes disambiguating the per-request client_claims (a JSON string forwarded in the request) from the pre-existing constructor client_claims (a dict of claims signed into the client-assertion JWT). - Add unit tests for non-string client_claims on managed identity and for non-string inputs to _parse_claims_or_raise. Co-authored-by: Copilot <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.
Summary
Port of msal-dotnet PR 5999 (
WithClaimsFromClient) into msal-python.Adds a
client_claimskeyword argument that forwards client-originated claims (for example the network security perimeterxms_az_nwperimidclaim) to ESTS / IMDS.In .NET,
WithClaimsFromClientis defined on the confidential-client builder base, so it applies to every confidential client flow. This port matches that scope:Confidential client —
msal/application.pyacquire_token_for_clientacquire_token_on_behalf_of(OBO)acquire_token_by_user_federated_identity_credential(user FIC)acquire_token_by_authorization_codeacquire_token_silent/acquire_token_silent_with_error(cache-read isolation + refresh-token request merge)Managed identity —
msal/managed_identity.pyManagedIdentityClient.acquire_token_for_client(IMDS / Azure VM source only)Key difference from
claims_challengeUnlike
claims_challenge(server-issued, which bypasses the cache),client_claimstokens are cached, and the cache entry is keyed on the claims value. This reuses the existing_compute_ext_cache_keycache-isolation mechanism (the same pattern asfmi_path):claimsstays excluded from the extended cache key, whileclient_claimsparticipates in it. Isolation is bidirectional — a request carryingclient_claimsnever reads a plain cached token, and a plain request never reads aclient_claimstoken.Naming: per-request
client_claimsvs the constructorclient_claimsClientApplication.__init__already has an unrelatedclient_claimsparameter — a dict of extra claims signed into the client-assertion JWT. The new per-requestclient_claimsis a JSON string of claims forwarded in the token request. They are different concepts and never share a scope; the name is kept (mirroring the "claims from client" intent), and both docstrings now carry cross-referencing notes to disambiguate them.Changes
msal/token_cache.py_parse_claims_or_raise,_deep_merge_dict,_merge_claimshelpers._parse_claims_or_raiseraises a friendlyValueErrorfor both malformed JSON and non-string input (it catches theTypeErrorfromjson.loadstoo).claimsstays excluded from the extended cache key;client_claimsparticipates.msal/oauth2cli/oauth2.pyclient_claimspseudo-parameter from the wire body, while preserving it for the cache-add event.msal/application.py_stash_client_claims()helper validatesclient_claims(must be a JSON string) and stashes it into the requestdata(so it contributes to the extended cache key and is merged into the OAuthclaimsparameter, then stripped from the wire). Used consistently by every confidential client flow above, includingacquire_token_for_client. The silent path stashesclient_claimsfor cache-read isolation and merges it into the refresh-token request; the RT-refresh path popsdataonce before the candidate-RT loop so the value applies across all candidate RTs. Docstrings disambiguate the per-request parameter from the constructorclient_claims.msal/managed_identity.pyclient_claimson the IMDS (Azure VM) source only (sent as theclaimsquery parameter); other sources raise a clear error; on MSIv1 the claims JSON may contain only thexms_az_nwperimidkey. Non-stringclient_claimsis rejected with aValueError, consistent with the confidential-client flows.Behavior notes
client_claimsvalues produce separate cache entries — use stable, non-dynamic values to avoid unbounded cache growth.client_claimsmerges with capability-derived claims andclaims_challengeinto a singleclaimsparameter.client_claimsraises aValueErroron every flow (confidential client and managed identity).client_claimsis absent: the merge is a no-op and the wire/cache paths are unaffected.Testing
Added unit tests covering each flow: wire shape (merged
claimspresent, noclient_claimsbody leak), input validation (non-string / invalid JSON, including non-strtypes), cache isolation (same value → cache hit; different value or plain request → isolated), plus the MI source restrictions and MSIv1 validation.