Skip to content

Use MSAL's recent UserFIC API for agentic flows#3842

Merged
Avery-Dunn merged 30 commits into
masterfrom
avdunn/agentic-fic-scenario-fix
Jul 13, 2026
Merged

Use MSAL's recent UserFIC API for agentic flows#3842
Avery-Dunn merged 30 commits into
masterfrom
avdunn/agentic-fic-scenario-fix

Conversation

@Avery-Dunn

@Avery-Dunn Avery-Dunn commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Replaces the internal ROPC piggybacking mechanism for agentic User FIC token acquisition with MSAL .NET's native AcquireTokenByUserFederatedIdentityCredential API. This resolves a customer-reported caching bug (#3840), modernizes the agentic flow to use purpose-built MSAL APIs, and simplifies the implementation.

Background

ID Web's agentic User FIC flow previously hijacked the ROPC (AcquireTokenByUsernamePassword) path via an internal add-in (AgentUserIdentityMsalAddIn) that rewrote HTTP request bodies at the last moment. This had several drawbacks:

  • No cache support: ROPC's silent-flow guard requires a non-null ClaimsPrincipal with oid/tid claims. In agentic scenarios, ClaimsPrincipal is typically null, so the cache was always bypassed — causing 2–4 unnecessary network calls per request (#3840).
  • Fragile HTTP body manipulation: Opaque, hard to debug, bypasses MSAL's validation and telemetry.
  • No type safety: String manipulation of grant types rather than MSAL's typed API surface.

MSAL .NET's AcquireTokenByUserFederatedIdentityCredential is a first-class API for this scenario with built-in cache support. Version 4.84.2 added the Guid userObjectId overload for OID-based flows.

Approach

Multi-CCA Pattern

Component Role
Blueprint CCA ID Web's existing CCA. Handles Leg 1: acquires FMI token (T1) via AcquireTokenForClient + WithFmiPath(agentAppId).
Agent CCA Per-agent CCA whose client assertion callback chains to the blueprint for Leg 1. Handles Leg 2 (AcquireTokenForClient → T2) and Leg 3 (AcquireTokenByUserFederatedIdentityCredential → user token).

Both use MSAL's shared static cache (EnableSharedCacheOptions), providing natural cache key isolation via distinct ClientId values while ensuring tokens survive CCA re-creation.

Three-Leg Flow

1. Caller → CreateAuthorizationHeaderForUserAsync(scopes, options, claimsPrincipal: null)
2. TryGetAuthenticationResultForAgentUserFicAsync detects agentic flow (UPN or OID)
3. Silent lookup via stored account identifier → cache HIT? Return cached token.
4. Cache MISS:
   Leg 1: Blueprint CCA → AcquireTokenForClient + WithFmiPath → T1 (FMI token)
   Leg 2: Agent CCA → AcquireTokenForClient (T1 as client assertion) → T2
   Leg 3: Agent CCA → AcquireTokenByUserFederatedIdentityCredential(scopes, UPN/OID, T2) → user token
5. Store account identifier for future silent lookups
6. Return user token

On subsequent calls for the same (agent, user, tenant) tuple, step 3 returns the cached token with zero network calls.

Account Identifier Storage

A ConcurrentDictionary<string, string> maps "{agentAppId}:{USER_IDENTIFIER}:{TENANTID}" → MSAL account identifier. This replaces the role that ClaimsPrincipal oid/tid claims serve in other ID Web flows. Entries are cleaned up when the CCA dictionary is cleared at the size threshold.

Agent CCA Eviction

As DOS protection, the agent CCA dictionary is cleared entirely when it exceeds a configurable threshold (default 10,000). Since all agent CCAs use MSAL's shared static cache, clearing the dictionary only discards lightweight CCA objects — tokens remain accessible to newly-built CCAs via AcquireTokenSilent.

Tenant Propagation

The assertion callback extracts the tenant from AssertionRequestOptions.TokenEndpoint (when the host matches the configured instance) and applies WithTenantId to Leg 1. This ensures multi-tenant scenarios work correctly, matching the pattern used by OidcIdpSignedAssertionProvider.

Changes

Directory.Build.props

  • Bump MSAL .NET from 4.84.1 → 4.84.2 (adds Guid userObjectId overload)

TokenAcquisition.cs

  • TryGetAuthenticationResultForAgentUserFicAsync (new): Detects UPN/OID agentic flows, performs silent retrieval or the 3-leg flow via native MSAL APIs
  • GetOrBuildAgentUserFicCcaAsync (new): Builds and caches agent CCAs with assertion callbacks that chain to the blueprint CCA; applies shared cache and size-threshold eviction
  • ExtractTenantFromTokenEndpointIfSameInstance (new): Extracts tenant from token endpoint URL when host matches configured instance
  • Early return in TryGetAuthenticationResultForConfidentialClientUsingRopcAsync: Intercepts agentic flows before the ROPC path
  • Silent catch broadened: MsalException instead of MsalUiRequiredException
  • Dead code removal: Agent identity extraction blocks from the ROPC method

TokenAcquisition.Logger.cs

  • 6 structured log messages (LoggerMessage.Define):
    • AgentUserFicFlowDetected, AgentUserFicSilentSuccess, AgentUserFicSilentFailure
    • AgentUserFicAcquisitionComplete, AgentCcaCreated, AgentCcaEviction

LoggingEventId.cs

  • 6 new event IDs (600–605)

AgentIdentitiesExtension.cs

  • Removed add-in callback registration from AddAgentIdentities() (AddOidcFic() preserved)

AgentUserIdentityMsalAddIn.cs

  • Deleted. Fully superseded by native MSAL API usage.

TokenAcquisitionTests.cs

  • 4 core agent User FIC tests: UPN caching, OID caching, ClaimsPrincipal independence, UPN/OID isolation
  • 3 shared cache isolation tests: Multi-agent/multi-user correctness, per-instance eviction behavior, shared cache token survival across CCA re-creation
  • 1 size-threshold eviction test: Verifies dictionary clear at threshold
  • 4 ExtractTenantFromTokenEndpointIfSameInstance tests: Same/different instance, null inputs, invalid URI

No Breaking Changes

All public APIs are unchanged:

  • WithAgentUserIdentity(options, agentAppId, username) — now uses native UPN path internally
  • WithAgentUserIdentity(options, agentAppId, userId) — now uses native OID path internally
  • AddAgentIdentities() — still registers OidcFic; no longer registers the (internal) add-in callback

The deleted AgentUserIdentityMsalAddIn was internal static with no external consumers.

Known Limitations

  • National cloud FIC scope: Hardcoded to api://AzureADTokenExchange/.default (public cloud). National cloud support requires cloud-aware inference in MSAL itself — to be addressed in a follow-up.

Resolves

Replace ROPC piggybacking with MSAL's native
AcquireTokenByUserFederatedIdentityCredential API using the multi-CCA
pattern (blueprint + per-agent CCAs with assertion callbacks).

This enables proper token caching for agentic User FIC flows when
ClaimsPrincipal is null, eliminating 2-4 unnecessary network round-trips
per bot message.

Phase 1: UPN-based flows only. OID-based flows remain on the existing
ROPC+add-in path pending MSAL .NET support for the OID overload.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Avery-Dunn
Avery-Dunn requested a review from a team as a code owner June 4, 2026 16:56
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
@Avery-Dunn
Avery-Dunn marked this pull request as draft June 4, 2026 20:04
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
Avery-Dunn and others added 2 commits June 5, 2026 06:29
- Bump MSAL .NET from 4.84.1 to 4.84.2 (adds Guid userObjectId overload
  for AcquireTokenByUserFederatedIdentityCredential)
- Extend TryGetAuthenticationResultForAgentUserFicAsync to handle both
  UPN-based and OID-based agentic flows via native MSAL APIs
- Remove AgentUserIdentityMsalAddIn (ROPC body-rewriting workaround) and
  its registration in AddAgentIdentities — no longer needed
- Remove dead agent identity extraction code from ROPC path
- Add 3 OID-specific tests: cache on second call, fresh ClaimsPrincipal
  per call, and UPN/OID cache isolation

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 modernizes the agentic “User FIC” token acquisition flow by replacing the prior ROPC piggybacking/request-rewrite add-in with MSAL’s native AcquireTokenByUserFederatedIdentityCredential API, aiming to restore proper MSAL cache usage and fix the cache-bypass reported in #3840.

Changes:

  • Bumps MSAL .NET to 4.84.2 and switches agentic user token acquisition to native UserFIC (multi-CCA / 3-leg flow).
  • Adds internal caching structures for per-agent CCA instances and MSAL account identifiers to enable silent token acquisition even when ClaimsPrincipal is null.
  • Removes the internal MSAL add-in that rewrote ROPC requests and adds new unit tests covering UPN/OID cache behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Directory.Build.props Updates MSAL .NET version to enable the needed UserFIC overload.
src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Implements native UserFIC flow, agent CCA caching, and account-id mapping for silent cache hits.
src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs Adds an internal key for overriding the token-exchange audience via ExtraParameters.
src/Microsoft.Identity.Web.AgentIdentities/AgentIdentitiesExtension.cs Stops registering the old ROPC-rewrite callback in AddAgentIdentities().
src/Microsoft.Identity.Web.AgentIdentities/AgentUserIdentityMsalAddIn.cs Deletes the internal add-in that rewrote token requests.
tests/Microsoft.Identity.Web.Test/TokenAcquisitionTests.cs Adds new tests for agentic UserFIC caching behavior (UPN + OID).

Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
Comment thread tests/Microsoft.Identity.Web.Test/TokenAcquisitionTests.cs
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
@Avery-Dunn
Avery-Dunn marked this pull request as ready for review June 5, 2026 15:30
@Avery-Dunn Avery-Dunn changed the title Fix #3840: Use MSAL's UserFIC API for agentic UPN flows Use MSAL's UserFIC API for agentic flows Jun 5, 2026
@Avery-Dunn Avery-Dunn changed the title Use MSAL's UserFIC API for agentic flows Use MSAL's recent UserFIC API for agentic flows Jun 5, 2026
Avery-Dunn and others added 5 commits June 5, 2026 09:32
Replace ROPC piggybacking with MSAL's native
AcquireTokenByUserFederatedIdentityCredential API using the multi-CCA
pattern (blueprint + per-agent CCAs with assertion callbacks).

This enables proper token caching for agentic User FIC flows when
ClaimsPrincipal is null, eliminating 2-4 unnecessary network round-trips
per bot message.

Phase 1: UPN-based flows only. OID-based flows remain on the existing
ROPC+add-in path pending MSAL .NET support for the OID overload.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Bump MSAL .NET from 4.84.1 to 4.84.2 (adds Guid userObjectId overload
  for AcquireTokenByUserFederatedIdentityCredential)
- Extend TryGetAuthenticationResultForAgentUserFicAsync to handle both
  UPN-based and OID-based agentic flows via native MSAL APIs
- Remove AgentUserIdentityMsalAddIn (ROPC body-rewriting workaround) and
  its registration in AddAgentIdentities — no longer needed
- Remove dead agent identity extraction code from ROPC path
- Add 3 OID-specific tests: cache on second call, fresh ClaimsPrincipal
  per call, and UPN/OID cache isolation

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 8 out of 9 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • src/Microsoft.Identity.Web.UI/Microsoft.Identity.Web.UI.xml: Generated file

Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
Comment thread src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Outdated
This was referenced Jul 20, 2026
github-actions Bot pushed a commit to EelcoLos/nx-tinkering that referenced this pull request Jul 21, 2026
Pinned
[Microsoft.Identity.Web](https://github.com/AzureAD/microsoft-identity-web)
at 4.13.2.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.Identity.Web's
releases](https://github.com/AzureAD/microsoft-identity-web/releases)._

## 4.13.2

## What's Changed
* Apply reserved-header handling on the request-clone path and cover all
X-MS-TOKEN- headers by @​iNinja in
AzureAD/microsoft-identity-web#3915
* Restore independent PR pipeline + pool-aware MI identity + net462/472
unit tests by @​iarekk in
AzureAD/microsoft-identity-web#3935
* Post-release 4.13.0: changelog and public API shipped move by
@​neha-bhargava in
AzureAD/microsoft-identity-web#3937
* Remove redundant 'Run unit tests' GitHub Action by @​iarekk in
AzureAD/microsoft-identity-web#3939
* Apply consistent redirect-URI validation on AccountController.SignIn
by @​iNinja in
AzureAD/microsoft-identity-web#3940
* Fix duplicate logging of MsalUiRequiredException (in-repo copy of
#​3910) by @​iarekk in
AzureAD/microsoft-identity-web#3941
* Use MSAL's recent UserFIC API for agentic flows by @​Avery-Dunn in
AzureAD/microsoft-identity-web#3842
* Restore CustomizeHttpRequestMessage to run after the authorization
header by @​neha-bhargava in
AzureAD/microsoft-identity-web#3943
* Bump Microsoft.IdentityModel.Tokens.Saml from 5.7.0 to 8.19.1 by
@​dependabot[bot] in
AzureAD/microsoft-identity-web#3909
* Revert #​3909: keep OWIN Saml/WsFederation on 5.7.0 by @​iarekk in
AzureAD/microsoft-identity-web#3944
* Bump Microsoft.Identity.Abstractions from 12.4.0 to 12.5.0 by
@​neha-bhargava in
AzureAD/microsoft-identity-web#3947
* Add OnBeforeAuthHeaderCreation / OnAfterAuthHeaderCreation hooks to
DownstreamApi by @​neha-bhargava in
AzureAD/microsoft-identity-web#3942
* Update IdentityModelV5Version and SamlPackageVersion to 5.7.1 in proj…
by @​trwalke in
AzureAD/microsoft-identity-web#3950
* Rename retired MSALMSIV2 agent pool to MISEManagedIdentity by
@​gladjohn with @​Copilot in
AzureAD/microsoft-identity-web#3949
* Improve IDW10109 error handling for credential loading failures by
@​Avery-Dunn in
AzureAD/microsoft-identity-web#3946
* Bump MSAL dependencies to 4.86.1 in central props by @​gladjohn with
@​Copilot in AzureAD/microsoft-identity-web#3953
* Bump the notsecurity group with 3 updates by @​dependabot[bot] in
AzureAD/microsoft-identity-web#3954


**Full Changelog**:
AzureAD/microsoft-identity-web@4.13.0...4.13.2

Commits viewable in [compare
view](AzureAD/microsoft-identity-web@4.13.0...4.13.2).
</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@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.

5 participants