Skip to content

[Feature Request] Add WithClientClaims() API for client-originated claims (NSP scenario) #5965

@gladjohn

Description

@gladjohn

MSAL client type

Confidential, Managed identity

Problem statement

Who is affected

Azure Resource Providers (RPs) running as backing services — e.g., Azure Redis Cache VMs using Managed Identity to acquire tokens. This is not an end-user API.

What happens today

An RP needs to include an NSP (Network Security Perimeter) claim in every token request so the resulting token is scoped to a specific network perimeter. The claim is stable — the same value is sent on every request for the lifetime of the workload.

MSAL has two APIs, and neither works for this scenario:

WithClaims(string claims) — Designed for server-initiated claims challenges (MFA, CAE). MSAL intentionally bypasses the token cache when claims are present, because challenges are one-time events. But NSP claims are stable and repeated. Using WithClaims means every call hits IMDS/ESTS — a performance disaster at scale, and a throttling risk.

WithExtraQueryParameters() — Exists on ManagedIdentityApplicationBuilder (hidden, [EditorBrowsable(Never)]). Passes the parameter to IMDS, but MSAL's cache ignores it — the parameter is not part of the cache key. Result: first call gets a token with the NSP claim, second call returns the cached token even if different claims are passed. Wrong token served silently.

The gap

There is no MSAL API that says: "Here is a claim from the client. Include it in the token request AND in the cache key so tokens are correctly partitioned by claim value."

Impact

  • High-severity ICMs due to incorrect NSP claim handling
  • RPs forced to choose between: cache miss on every call (WithClaims) or wrong token reuse (WithExtraQueryParameters)
  • No safe production path without building a custom token cache layer outside MSAL

Solution

New API: WithClientClaims(string claimsJson)

Note: The name WithClientClaims already exists on ConfidentialClientApplicationBuilder for a different purpose (signing claims into the JWT client assertion). The new API must use a distinct name to avoid collision. Options:

  • WithClaimsFromClient(string claimsJson)
  • WithRequestClaims(string claimsJson)
  • WithAdditionalClaims(string claimsJson)

Decision needed — using WithClientClaims as placeholder below.

Add a method to the token acquisition builders that:

  1. Forwards the claims JSON to the token endpoint (IMDS or ESTS)
  2. Includes the claims value in the MSAL cache key
  3. JSON-merges with any existing claims challenges (from WithClaims())
// Managed Identity — primary scenario (Azure Redis Cache RP)
var result = await miApp
    .AcquireTokenForManagedIdentity(resource)
    .WithClientClaims(nspClaimsJson)
    .ExecuteAsync();

// Client Credentials — harmonized API
var result = await ccApp
    .AcquireTokenForClient(scopes)
    .WithClientClaims(nspClaimsJson)
    .ExecuteAsync();

Claims format

The claims JSON must follow the OIDC claims request format defined in Section 5.5 of OpenID Connect Core 1.0.

The claims parameter is a JSON object with top-level members (access_token, id_token, userinfo) each containing individual claim requests:

{
  "access_token": {
    "xms_nsp_id": {
      "essential": true,
      "value": "<network-perimeter-id>"
    }
  }
}

Each individual claim request supports (per Section 5.5.1):

Property Type Meaning
null Voluntary claim, default manner
essential boolean If true, the claim is essential to the request
value string Request a specific claim value
values string[] Request one of a set of values, in preference order

Cache behavior

Behavior Detail
Tokens are cached Yes — unlike WithClaims() which bypasses cache
Claims value in cache key Yes — different claims produce different cache entries
Same claims = cache hit Yes — stable NSP claims reuse cached tokens
Dynamic data risk If claims contain timestamps or nonces, each unique value creates a new cache entry. Callers must ensure claims are stable. MSAL should log a warning if claim values change frequently.

Claims merging

Because claims is a reserved OIDC parameter, MSAL cannot send two separate claims values. When both WithClaims() (server challenge) and WithClientClaims() (client-originated) are used, MSAL must JSON-merge them into a single OIDC-compliant claims object:

Final merged claims = claims challenges (WithClaims) ∪ client claims (WithClientClaims)

MSAL already performs a similar merge today: ClaimsHelper.GetMergedClaimsAndClientCapabilities() merges claims challenges with client capabilities. The WithClientClaims path extends this merge to include a third source.

Scope

This issue:

Flow Builder Priority
MSI v1 (IMDS) AcquireTokenForManagedIdentity Primary — NSP scenario
Client Credentials AcquireTokenForClient Harmonize

Future (separate issues):

Flow Builder Notes
MSI v2 (IMDS V2 / mTLS) AcquireTokenForManagedIdentity IMDS v2 doesn't support NSP claims yet
FIC AcquireTokenForClient + MI assertion Needs investigation

Open Questions

  1. IMDS parameter format — Does IMDS accept the standard OIDC claims parameter format? Or does it expect a distinct parameter name/format? If IMDS uses a distinct parameter (e.g., nsp_claims), JSON merging is not required. Action: Confirm with IMDS team (Nidhi / Qi).

  2. API namingWithClientClaims already exists on ConfidentialClientApplicationBuilder for JWT assertion claims (line 185-202). Need a distinct name for the new API. See options above.

  3. Existing client_credentials NSP support — MSAL already supports NSP claims for client_credentials as an extra param in the client_assertion with cache keying. How does this new API relate? Should it replace the existing approach?


References

  • OIDC Claims Request Parameter: OpenID Connect Core Section 5.5
  • OIDC Individual Claims Requests: Section 5.5.1
  • Existing MSAL claims merge: src/client/Microsoft.Identity.Client/Internal/ClaimsHelper.csGetMergedClaimsAndClientCapabilities()
  • Existing WithClientClaims (assertion signing): ConfidentialClientApplicationBuilder.cs lines 185-202
  • Design doc: IMDS Support for NSP-Scoped Delegated Identity Tokens (Azure MSI team)

Metadata

Metadata

No fields configured for Feature.

Projects

Status

Committed High Priority

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions