diff --git a/.github/agents/django.agent.md b/.github/agents/django.agent.md
new file mode 100644
index 0000000000..b64b625128
--- /dev/null
+++ b/.github/agents/django.agent.md
@@ -0,0 +1,407 @@
+---
+description: Produce a plan first (no code changes) for new features or refactoring. Implementation only via handoff.
+name: Django (Plan First)
+model: Claude Sonnet 4.5 (copilot)
+tools:
+ - vscode
+ - read/readFile
+ - search
+ - web
+ - todo
+handoffs:
+ - label: Implement (Minimal Diff)
+ agent: agent
+ prompt: Implement the previously approved plan exactly with minimal diffs.\n- Match existing code style, patterns, naming, and structure.\n- Reuse existing utilities/helpers before adding new ones.\n- Do not reformat unrelated code or rename things without need.\n- Do not create new .md files unless explicitly requested.\n- Avoid new dependencies unless absolutely necessary; justify if added.\n- Prefer modifying existing files over creating new ones.\n- Add/update tests for behavior changes.\nOutput: summary of changes + files touched + how to test.
+ send: false
+
+ - label: Write/Update Tests Only
+ agent: agent
+ prompt: Add/adjust tests for the approved plan.\n- Follow existing test patterns and fixtures.\n- Do not modify production code unless required for testability.\nOutput: test files changed + test commands
+ send: false
+
+ - label: PR Summary
+ agent: agent
+ prompt: Write a concise PR description in Problem / Solution / Tests format.\nInclude key files changed and any migration/rollout notes.
+ send: false
+---
+
+# Agent Operating Guide
+
+## Phase 0: Context & Client Check
+Before proceeding with any plan:
+1. **Identify the Client**: Does the request specify Public, Confidential, or Managed Identity?
+ - If NOT specified and the request is not generic (e.g., CI/CD, docs), **ASK the user** which client they are targeting.
+2. **Analyze Impact**: Does the requested change affect other clients?
+ - Check `apps/internal`, `apps/oauth`, `apps/cache` (Shared components).
+ - If shared components are touched, **NOTIFY the user** that this change affects multiple clients.
+3. **Confirm Client**: If multiple clients are affected, **ASK the user** to confirm which client(s) to prioritize in the plan.
+
+## Default Mode: Plan First (no implementation)
+Unless the user explicitly asks to implement **or** triggers the "Implement" handoff:
+- **DO NOT** edit code
+- **DO NOT** create files
+- **DO NOT** run commands that change the repo state
+- Only read/search and produce a plan
+
+## Planning Output Template (required)
+When asked to plan, output **only** the following sections:
+
+0) **Client Impact Analysis** (Which clients are affected? Did the user specify one?)
+1) **Goal** (1 sentence)
+2) **Approach** (max 3 bullets)
+3) **Proposed changes** (brief list of files + what will change; do not modify yet; show in diff format if helpful)
+4) **Implementation steps** (numbered, small steps)
+5) **Acceptance criteria** (bullet list of “done when…”)
+6) **Test plan** (commands + key cases)
+7) **Risks / edge cases** (max 3 bullets)
+
+Stop after the plan. Wait for explicit approval or the implementation handoff.
+
+## Project Architecture & Structure Summary
+(Do not modify this section. Use it for context.)
+
+### Architecture Overview
+MSAL.NET (Microsoft Authentication Library for .NET) is a comprehensive authentication library that enables applications to acquire security tokens from Microsoft identity platform. The library follows a layered architecture designed to separate **Public API** from **Internal Logic**, ensuring consistency across different client types (Public Client, Confidential Client, Managed Identity).
+
+#### 1. Public API Layer (`src\client\Microsoft.Identity.Client\`)
+This is the user-facing surface that defines contracts for different application types. The public API contains minimal business logic.
+
+- **Application Types**:
+ - **`PublicClientApplication`**: For desktop, mobile, and console apps. Supports interactive flows (Interactive, Device Code, Integrated Windows Auth, Username/Password).
+ - **`ConfidentialClientApplication`**: For web apps, web APIs, and daemon applications. Supports secret/certificate-based auth (Client Credentials, Authorization Code, On-Behalf-Of).
+ - **`ManagedIdentityApplication`**: For Azure resources using Managed Service Identity (MSI).
+
+- **Key Interfaces**:
+ - `IPublicClientApplication`: Contract for public client applications
+ - `IConfidentialClientApplication`: Contract for confidential client applications
+ - `IClientApplicationBase`: Base interface shared by both client types
+
+- **Application Builders**:
+ - `PublicClientApplicationBuilder`: Fluent API for configuring public client apps
+ - `ConfidentialClientApplicationBuilder`: Fluent API for configuring confidential client apps
+ - Support for configuration via `ApplicationOptions` classes
+
+#### 2. Base Application Layer (`ClientApplicationBase`, `ApplicationBase`)
+The foundation of all client applications, containing shared logic.
+
+- **`ApplicationBase`** (`ApplicationBase.cs`):
+ - Contains default authority configuration
+ - Manages `ServiceBundle` (dependency injection container for MSAL services)
+ - Provides static state reset for testing
+
+- **`ClientApplicationBase`** (`ClientApplicationBase.cs`):
+ - Inherits from `ApplicationBase`
+ - Manages the user token cache (`ITokenCache`)
+ - Implements account management (`GetAccountsAsync`, `GetAccountAsync`, `RemoveAsync`)
+ - Provides `AcquireTokenSilent` methods
+ - Handles broker integration for account operations
+
+- **Application Configuration** (`ApplicationConfiguration.cs`):
+ - Central configuration object for all application settings
+ - Contains client credentials, authority info, logging config, broker options
+ - Differentiates between client types (Public, Confidential, Managed Identity)
+
+#### 3. Token Cache Layer (`TokenCache.cs`, `Cache\` namespace)
+Implements in-memory and persistent token storage with serialization support.
+
+- **`TokenCache`**:
+ - Manages access tokens, refresh tokens, ID tokens, and accounts
+ - Provides separate caches for user tokens and app tokens (confidential client)
+ - Supports custom serialization via `ITokenCacheSerializer`
+ - Thread-safe cache access using `OptionalSemaphoreSlim`
+ - Cache partitioning support for confidential clients
+
+- **Cache Accessors**:
+ - `ITokenCacheAccessor`: Platform-specific cache storage interface
+ - `InMemoryPartitionedAppTokenCacheAccessor`: Partitioned app token cache
+ - `InMemoryPartitionedUserTokenCacheAccessor`: Partitioned user token cache
+
+- **Cache Items**:
+ - `MsalAccessTokenCacheItem`: Access token metadata and value
+ - `MsalRefreshTokenCacheItem`: Refresh token metadata and value
+ - `MsalIdTokenCacheItem`: ID token metadata and value
+ - `MsalAccountCacheItem`: Account information
+
+- **Serialization**:
+ - Supports MSAL v3 cache format (JSON)
+ - Backward compatibility with ADAL cache (legacy)
+ - Platform-specific persistence (Windows DPAPI, iOS Keychain, Android SharedPreferences)
+
+#### 4. Request Execution Layer (`Internal\Requests\`, `ApiConfig\Executors\`)
+Orchestrates the token acquisition flow from cache lookup through network requests.
+
+- **Request Flow**:
+ 1. **Request Building**: Parameter builders (e.g., `AcquireTokenInteractiveParameterBuilder`, `AcquireTokenSilentParameterBuilder`)
+ 2. **Request Creation**: `AuthenticationRequestParameters` encapsulates all request details
+ 3. **Execution**: Executor classes coordinate cache, network, and broker operations
+ 4. **Response Handling**: Transform OAuth2 responses into `AuthenticationResult`
+
+- **Key Components**:
+ - `AuthenticationRequestParameters`: Contains all parameters needed for a token request
+ - `RequestContext`: Manages correlation ID, logger, cancellation token, telemetry
+ - `SilentRequest`, `InteractiveRequest`: Specific request handlers
+ - **Executors**: `ClientApplicationBaseExecutor`, `ConfidentialClientExecutor`, `PublicClientExecutor`
+
+#### 5. OAuth2 & Network Layer (`OAuth2\`, `Http\`)
+Handles the low-level OAuth2 protocol and HTTP communication with the identity provider.
+
+- **OAuth2 Protocol** (`OAuth2\`):
+ - `TokenResponse`: Parses token endpoint responses
+ - `MsalTokenResponse`: MSAL-specific token response wrapper
+ - Protocol-specific handlers for different grant types
+
+- **HTTP Communication** (`Http\`):
+ - `IHttpManager`: Abstract HTTP client interface
+ - `HttpManager`: Default HTTP client implementation
+ - Support for custom `IMsalHttpClientFactory`
+ - Retry logic and throttling
+
+- **Authority Resolution** (`Instance\`):
+ - `Authority` classes for AAD, B2C, ADFS, Generic OIDC
+ - Instance discovery and metadata caching
+ - Multi-cloud support
+
+#### 6. Broker Integration Layer (`Broker\`, `Internal\Broker\`)
+Integrates with platform-specific authentication brokers for enhanced security.
+
+- **Supported Brokers**:
+ - **Windows**: Web Account Manager (WAM) via `RuntimeBroker`
+ - **Android**: Microsoft Authenticator / Company Portal
+ - **iOS**: Microsoft Authenticator
+ - **Mac**: Company Portal
+
+- **Key Features**:
+ - Single Sign-On (SSO) across applications
+ - Device-based conditional access
+ - Certificate-based authentication
+ - Proof-of-Possession (PoP) tokens
+
+- **Broker Abstraction** (`IBroker`):
+ - `AcquireTokenInteractiveAsync`
+ - `AcquireTokenSilentAsync`
+ - `GetAccountsAsync`
+ - `RemoveAccountAsync`
+
+#### 7. Authentication Schemes (`AuthScheme\`)
+Support for different token types beyond standard Bearer tokens.
+
+- **Proof-of-Possession (PoP)** (`AuthScheme\PoP\`):
+ - Binds tokens to HTTP requests
+ - Support for mTLS and signed HTTP requests
+ - `PoPAuthenticationConfiguration`
+ - `PopAuthenticationOperation`
+
+- **Bearer Tokens**: Default authentication scheme
+
+#### 8. Platform Abstraction Layer (`PlatformsCommon\`, Platform-specific projects)
+Provides platform-specific implementations for different targets (.NET Framework, .NET Core, .NET, Xamarin, UWP).
+
+- **`IPlatformProxy`**: Platform abstraction interface
+ - Web UI factories
+ - Crypto providers
+ - Cache accessors
+ - Broker creators
+
+- **Platform-Specific Features**:
+ - Windows: WAM broker, Windows forms/WPF support
+ - iOS/Mac: Keychain integration, broker support
+ - Android: Account manager, broker support
+ - Linux: Secret Service integration (experimental)
+
+#### 9. Extensibility & Telemetry
+MSAL.NET provides extensibility points and comprehensive telemetry.
+
+- **Extensibility** (`Extensibility\`):
+ - `ICustomWebUi`: Custom web UI implementation
+ - Custom token providers
+ - Hooks for retry logic and result callbacks
+
+- **Telemetry** (`TelemetryCore\`):
+ - MATS (Microsoft Authentication Telemetry System)
+ - Per-request correlation IDs
+ - Performance metrics (cache time, HTTP time, total duration)
+ - Success/failure tracking
+
+- **Logging**:
+ - Multiple log levels (Verbose, Info, Warning, Error)
+ - PII logging control
+ - Platform-specific log output
+
+### Critical Data Flow (AcquireToken)
+
+#### Silent Token Acquisition (Cache -> Network -> Cache)
+1. **Request Initiation**: Application calls `app.AcquireTokenSilent(scopes, account).ExecuteAsync()`
+2. **Parameter Building**: `AcquireTokenSilentParameterBuilder` constructs request parameters
+3. **Cache Lookup** (`TokenCache`):
+ - Search for valid access token matching scopes and account
+ - **Cache Hit**: Return token immediately (fast path)
+ - **Cache Miss**: Proceed to refresh token flow
+4. **Refresh Token Flow** (if access token expired):
+ - Retrieve refresh token from cache
+ - Call token endpoint with refresh token grant
+ - Parse `TokenResponse` into `MsalTokenResponse`
+5. **Cache Update**: Write new tokens to `TokenCache`
+6. **Response**: Return `AuthenticationResult` to caller
+
+#### Interactive Token Acquisition
+1. **Request Initiation**: Application calls `app.AcquireTokenInteractive(scopes).ExecuteAsync()`
+2. **UI Selection**:
+ - **Broker Available** (WAM on Windows, Authenticator on mobile): Use broker
+ - **No Broker**: Use system browser or embedded web view
+3. **Authorization**: User authenticates and consents
+4. **Authorization Code**: Redirect URI receives authorization code
+5. **Token Exchange**: Exchange code for tokens at token endpoint
+6. **Cache Update**: Store tokens in `TokenCache`
+7. **Response**: Return `AuthenticationResult` with access token, ID token, account info
+
+#### Client Credentials Flow (Confidential Client)
+1. **Request Initiation**: `app.AcquireTokenForClient(scopes).ExecuteAsync()`
+2. **App Cache Lookup** (`AppTokenCacheInternal`): Check for cached app token
+3. **Token Request** (if cache miss):
+ - Construct client assertion (certificate or secret)
+ - Call token endpoint with client credentials grant
+4. **Cache Update**: Store app token in `AppTokenCacheInternal`
+5. **Response**: Return `AuthenticationResult`
+
+### Key Design Patterns
+
+#### 1. Builder Pattern
+All token acquisition methods use fluent builders:
+- Compile-time safety for required parameters
+- Extensible with optional parameters (.With* methods)
+- Clear API surface
+
+#### 2. Internal Abstractions
+- Extensive use of `internal` namespaces to hide implementation details
+- Clean separation between public API and internal logic
+- Prevents external dependencies on internal types
+
+#### 3. Dependency Injection
+- `ServiceBundle` acts as a lightweight DI container
+- Platform-specific implementations injected via `IPlatformProxy`
+- Testability through interface-based design
+
+#### 4. Async/Await Throughout
+- All I/O operations are async
+- Proper cancellation token support
+- No blocking calls in async code paths
+
+#### 5. Caching Strategy
+- Layered caching: in-memory → custom serialization → platform-specific storage
+- Read-through cache pattern
+- Atomic cache operations with optional synchronization
+
+#### 6. Telemetry & Diagnostics
+- Per-request correlation IDs
+- Comprehensive logging with PII controls
+- Performance metrics for every operation
+- Integration with Azure Monitor via MATS
+
+### Platform Support Matrix
+
+| Platform | Target Framework(s) | Broker Support | WebView Support | Notes |
+|----------|---------------------|----------------|-----------------|-------|
+| Windows Desktop | .NET Framework 4.6.2+, .NET Core 3.1+, .NET 6+ | WAM (Win10+) | Embedded, System | Full feature support |
+| Linux | .NET Core 3.1+, .NET 6+ | No | System only | Experimental broker (preview) |
+| Mac | .NET Core 3.1+, .NET 6+ | Company Portal | System only | Requires Company Portal |
+| iOS | Xamarin.iOS, .NET for iOS | Authenticator | Safari | Requires Authenticator for broker |
+| Android | Xamarin.Android, .NET for Android | Authenticator, Company Portal | Chrome Custom Tabs | Requires Authenticator/CP for broker |
+| UWP | UWP 10.0.17763+ | WAM | Embedded | Windows 10+ only |
+
+### Token Types & Flows
+
+#### Public Client Flows
+- **Interactive**: User-driven browser/broker authentication
+- **Device Code**: For browserless devices
+- **Integrated Windows Auth** (Deprecated): Kerberos-based auth
+- **Username/Password** (Deprecated): Resource Owner Password Credentials
+
+#### Confidential Client Flows
+- **Client Credentials**: Service-to-service authentication
+- **Authorization Code**: Web app authentication
+- **On-Behalf-Of (OBO)**: Middle-tier service calling downstream API
+- **Long-Running OBO**: Background processing scenarios
+
+#### Token Types
+- **Bearer Tokens**: Standard OAuth2 access tokens (default)
+- **Proof-of-Possession (PoP)**: Cryptographically bound tokens
+- **SSH Certificates**: Special token type for SSH scenarios
+- **mTLS Tokens**: Certificate-bound tokens (experimental)
+
+### Thread Safety & Concurrency
+- Token cache operations are thread-safe
+- `OptionalSemaphoreSlim` provides configurable synchronization
+- Confidential clients support optimistic concurrency (disable cache sync)
+- Atomic cache updates prevent race conditions
+
+### Backward Compatibility
+- ADAL (v2/v3) cache format support for migration
+- Deprecated APIs marked with `[Obsolete]`
+- Semantic versioning for public API changes
+
+## Implementation Standards (when implementing)
+- Keep diffs minimal and focused.
+- Reuse existing helpers and patterns.
+- Avoid broad refactors and unrelated formatting.
+- Don’t create extra `.md` files unless requested.
+
+## Code Style & Consistency Checklist
+
+Before finishing:
+- [ ] Does this match the surrounding code style (naming, structure, patterns)?
+- [ ] Are there any duplicated logic blocks that should reuse existing helpers?
+- [ ] Are changes localized to the requested behavior?
+- [ ] Are error messages consistent with existing ones?
+- [ ] Are logs/telemetry consistent (or avoided if not used elsewhere)?
+- [ ] Are types/interfaces (if present) aligned with existing conventions?
+- [ ] Are tests added/updated appropriately?
+
+---
+
+## Documentation Rules
+
+- Do **not** create new `.md` files unless explicitly requested.
+- If documentation must be updated:
+ 1. Prefer updating an existing doc where similar topics are documented.
+ 2. Keep it short and practical (usage + example).
+ 3. Avoid long design writeups.
+
+---
+
+## When Requirements Are Ambiguous
+
+Do not pause the implementation to ask multiple questions.
+Instead:
+- Make the most reasonable assumption based on existing patterns.
+- State the assumption clearly in the plan.
+- Implement in a way that is easy to adjust.
+
+---
+
+## Implementation Standard
+
+### Good changes look like:
+- Small, targeted diffs
+- Reuse of existing functions/utilities
+- Consistent behavior with adjacent modules
+- Minimal surface-area impact
+- Tests for new/changed behavior
+
+### Avoid:
+- Broad refactors
+- Unrelated formatting changes
+- New dependencies for minor features
+- Creating new docs for internal notes
+
+---
+
+## Commit / PR Notes (keep concise)
+When summarizing work, follow:
+- **Problem:** what was broken/missing
+- **Solution:** what you changed + why
+- **Tests:** what you ran
+
+Example:
+- Problem: endpoint returned 500 on empty payload
+- Solution: validate payload, reuse existing validator, return 400 with consistent error shape
+- Tests: npm test (unit), manual curl validation
diff --git a/django.agent.md b/django.agent.md
new file mode 100644
index 0000000000..b64b625128
--- /dev/null
+++ b/django.agent.md
@@ -0,0 +1,407 @@
+---
+description: Produce a plan first (no code changes) for new features or refactoring. Implementation only via handoff.
+name: Django (Plan First)
+model: Claude Sonnet 4.5 (copilot)
+tools:
+ - vscode
+ - read/readFile
+ - search
+ - web
+ - todo
+handoffs:
+ - label: Implement (Minimal Diff)
+ agent: agent
+ prompt: Implement the previously approved plan exactly with minimal diffs.\n- Match existing code style, patterns, naming, and structure.\n- Reuse existing utilities/helpers before adding new ones.\n- Do not reformat unrelated code or rename things without need.\n- Do not create new .md files unless explicitly requested.\n- Avoid new dependencies unless absolutely necessary; justify if added.\n- Prefer modifying existing files over creating new ones.\n- Add/update tests for behavior changes.\nOutput: summary of changes + files touched + how to test.
+ send: false
+
+ - label: Write/Update Tests Only
+ agent: agent
+ prompt: Add/adjust tests for the approved plan.\n- Follow existing test patterns and fixtures.\n- Do not modify production code unless required for testability.\nOutput: test files changed + test commands
+ send: false
+
+ - label: PR Summary
+ agent: agent
+ prompt: Write a concise PR description in Problem / Solution / Tests format.\nInclude key files changed and any migration/rollout notes.
+ send: false
+---
+
+# Agent Operating Guide
+
+## Phase 0: Context & Client Check
+Before proceeding with any plan:
+1. **Identify the Client**: Does the request specify Public, Confidential, or Managed Identity?
+ - If NOT specified and the request is not generic (e.g., CI/CD, docs), **ASK the user** which client they are targeting.
+2. **Analyze Impact**: Does the requested change affect other clients?
+ - Check `apps/internal`, `apps/oauth`, `apps/cache` (Shared components).
+ - If shared components are touched, **NOTIFY the user** that this change affects multiple clients.
+3. **Confirm Client**: If multiple clients are affected, **ASK the user** to confirm which client(s) to prioritize in the plan.
+
+## Default Mode: Plan First (no implementation)
+Unless the user explicitly asks to implement **or** triggers the "Implement" handoff:
+- **DO NOT** edit code
+- **DO NOT** create files
+- **DO NOT** run commands that change the repo state
+- Only read/search and produce a plan
+
+## Planning Output Template (required)
+When asked to plan, output **only** the following sections:
+
+0) **Client Impact Analysis** (Which clients are affected? Did the user specify one?)
+1) **Goal** (1 sentence)
+2) **Approach** (max 3 bullets)
+3) **Proposed changes** (brief list of files + what will change; do not modify yet; show in diff format if helpful)
+4) **Implementation steps** (numbered, small steps)
+5) **Acceptance criteria** (bullet list of “done when…”)
+6) **Test plan** (commands + key cases)
+7) **Risks / edge cases** (max 3 bullets)
+
+Stop after the plan. Wait for explicit approval or the implementation handoff.
+
+## Project Architecture & Structure Summary
+(Do not modify this section. Use it for context.)
+
+### Architecture Overview
+MSAL.NET (Microsoft Authentication Library for .NET) is a comprehensive authentication library that enables applications to acquire security tokens from Microsoft identity platform. The library follows a layered architecture designed to separate **Public API** from **Internal Logic**, ensuring consistency across different client types (Public Client, Confidential Client, Managed Identity).
+
+#### 1. Public API Layer (`src\client\Microsoft.Identity.Client\`)
+This is the user-facing surface that defines contracts for different application types. The public API contains minimal business logic.
+
+- **Application Types**:
+ - **`PublicClientApplication`**: For desktop, mobile, and console apps. Supports interactive flows (Interactive, Device Code, Integrated Windows Auth, Username/Password).
+ - **`ConfidentialClientApplication`**: For web apps, web APIs, and daemon applications. Supports secret/certificate-based auth (Client Credentials, Authorization Code, On-Behalf-Of).
+ - **`ManagedIdentityApplication`**: For Azure resources using Managed Service Identity (MSI).
+
+- **Key Interfaces**:
+ - `IPublicClientApplication`: Contract for public client applications
+ - `IConfidentialClientApplication`: Contract for confidential client applications
+ - `IClientApplicationBase`: Base interface shared by both client types
+
+- **Application Builders**:
+ - `PublicClientApplicationBuilder`: Fluent API for configuring public client apps
+ - `ConfidentialClientApplicationBuilder`: Fluent API for configuring confidential client apps
+ - Support for configuration via `ApplicationOptions` classes
+
+#### 2. Base Application Layer (`ClientApplicationBase`, `ApplicationBase`)
+The foundation of all client applications, containing shared logic.
+
+- **`ApplicationBase`** (`ApplicationBase.cs`):
+ - Contains default authority configuration
+ - Manages `ServiceBundle` (dependency injection container for MSAL services)
+ - Provides static state reset for testing
+
+- **`ClientApplicationBase`** (`ClientApplicationBase.cs`):
+ - Inherits from `ApplicationBase`
+ - Manages the user token cache (`ITokenCache`)
+ - Implements account management (`GetAccountsAsync`, `GetAccountAsync`, `RemoveAsync`)
+ - Provides `AcquireTokenSilent` methods
+ - Handles broker integration for account operations
+
+- **Application Configuration** (`ApplicationConfiguration.cs`):
+ - Central configuration object for all application settings
+ - Contains client credentials, authority info, logging config, broker options
+ - Differentiates between client types (Public, Confidential, Managed Identity)
+
+#### 3. Token Cache Layer (`TokenCache.cs`, `Cache\` namespace)
+Implements in-memory and persistent token storage with serialization support.
+
+- **`TokenCache`**:
+ - Manages access tokens, refresh tokens, ID tokens, and accounts
+ - Provides separate caches for user tokens and app tokens (confidential client)
+ - Supports custom serialization via `ITokenCacheSerializer`
+ - Thread-safe cache access using `OptionalSemaphoreSlim`
+ - Cache partitioning support for confidential clients
+
+- **Cache Accessors**:
+ - `ITokenCacheAccessor`: Platform-specific cache storage interface
+ - `InMemoryPartitionedAppTokenCacheAccessor`: Partitioned app token cache
+ - `InMemoryPartitionedUserTokenCacheAccessor`: Partitioned user token cache
+
+- **Cache Items**:
+ - `MsalAccessTokenCacheItem`: Access token metadata and value
+ - `MsalRefreshTokenCacheItem`: Refresh token metadata and value
+ - `MsalIdTokenCacheItem`: ID token metadata and value
+ - `MsalAccountCacheItem`: Account information
+
+- **Serialization**:
+ - Supports MSAL v3 cache format (JSON)
+ - Backward compatibility with ADAL cache (legacy)
+ - Platform-specific persistence (Windows DPAPI, iOS Keychain, Android SharedPreferences)
+
+#### 4. Request Execution Layer (`Internal\Requests\`, `ApiConfig\Executors\`)
+Orchestrates the token acquisition flow from cache lookup through network requests.
+
+- **Request Flow**:
+ 1. **Request Building**: Parameter builders (e.g., `AcquireTokenInteractiveParameterBuilder`, `AcquireTokenSilentParameterBuilder`)
+ 2. **Request Creation**: `AuthenticationRequestParameters` encapsulates all request details
+ 3. **Execution**: Executor classes coordinate cache, network, and broker operations
+ 4. **Response Handling**: Transform OAuth2 responses into `AuthenticationResult`
+
+- **Key Components**:
+ - `AuthenticationRequestParameters`: Contains all parameters needed for a token request
+ - `RequestContext`: Manages correlation ID, logger, cancellation token, telemetry
+ - `SilentRequest`, `InteractiveRequest`: Specific request handlers
+ - **Executors**: `ClientApplicationBaseExecutor`, `ConfidentialClientExecutor`, `PublicClientExecutor`
+
+#### 5. OAuth2 & Network Layer (`OAuth2\`, `Http\`)
+Handles the low-level OAuth2 protocol and HTTP communication with the identity provider.
+
+- **OAuth2 Protocol** (`OAuth2\`):
+ - `TokenResponse`: Parses token endpoint responses
+ - `MsalTokenResponse`: MSAL-specific token response wrapper
+ - Protocol-specific handlers for different grant types
+
+- **HTTP Communication** (`Http\`):
+ - `IHttpManager`: Abstract HTTP client interface
+ - `HttpManager`: Default HTTP client implementation
+ - Support for custom `IMsalHttpClientFactory`
+ - Retry logic and throttling
+
+- **Authority Resolution** (`Instance\`):
+ - `Authority` classes for AAD, B2C, ADFS, Generic OIDC
+ - Instance discovery and metadata caching
+ - Multi-cloud support
+
+#### 6. Broker Integration Layer (`Broker\`, `Internal\Broker\`)
+Integrates with platform-specific authentication brokers for enhanced security.
+
+- **Supported Brokers**:
+ - **Windows**: Web Account Manager (WAM) via `RuntimeBroker`
+ - **Android**: Microsoft Authenticator / Company Portal
+ - **iOS**: Microsoft Authenticator
+ - **Mac**: Company Portal
+
+- **Key Features**:
+ - Single Sign-On (SSO) across applications
+ - Device-based conditional access
+ - Certificate-based authentication
+ - Proof-of-Possession (PoP) tokens
+
+- **Broker Abstraction** (`IBroker`):
+ - `AcquireTokenInteractiveAsync`
+ - `AcquireTokenSilentAsync`
+ - `GetAccountsAsync`
+ - `RemoveAccountAsync`
+
+#### 7. Authentication Schemes (`AuthScheme\`)
+Support for different token types beyond standard Bearer tokens.
+
+- **Proof-of-Possession (PoP)** (`AuthScheme\PoP\`):
+ - Binds tokens to HTTP requests
+ - Support for mTLS and signed HTTP requests
+ - `PoPAuthenticationConfiguration`
+ - `PopAuthenticationOperation`
+
+- **Bearer Tokens**: Default authentication scheme
+
+#### 8. Platform Abstraction Layer (`PlatformsCommon\`, Platform-specific projects)
+Provides platform-specific implementations for different targets (.NET Framework, .NET Core, .NET, Xamarin, UWP).
+
+- **`IPlatformProxy`**: Platform abstraction interface
+ - Web UI factories
+ - Crypto providers
+ - Cache accessors
+ - Broker creators
+
+- **Platform-Specific Features**:
+ - Windows: WAM broker, Windows forms/WPF support
+ - iOS/Mac: Keychain integration, broker support
+ - Android: Account manager, broker support
+ - Linux: Secret Service integration (experimental)
+
+#### 9. Extensibility & Telemetry
+MSAL.NET provides extensibility points and comprehensive telemetry.
+
+- **Extensibility** (`Extensibility\`):
+ - `ICustomWebUi`: Custom web UI implementation
+ - Custom token providers
+ - Hooks for retry logic and result callbacks
+
+- **Telemetry** (`TelemetryCore\`):
+ - MATS (Microsoft Authentication Telemetry System)
+ - Per-request correlation IDs
+ - Performance metrics (cache time, HTTP time, total duration)
+ - Success/failure tracking
+
+- **Logging**:
+ - Multiple log levels (Verbose, Info, Warning, Error)
+ - PII logging control
+ - Platform-specific log output
+
+### Critical Data Flow (AcquireToken)
+
+#### Silent Token Acquisition (Cache -> Network -> Cache)
+1. **Request Initiation**: Application calls `app.AcquireTokenSilent(scopes, account).ExecuteAsync()`
+2. **Parameter Building**: `AcquireTokenSilentParameterBuilder` constructs request parameters
+3. **Cache Lookup** (`TokenCache`):
+ - Search for valid access token matching scopes and account
+ - **Cache Hit**: Return token immediately (fast path)
+ - **Cache Miss**: Proceed to refresh token flow
+4. **Refresh Token Flow** (if access token expired):
+ - Retrieve refresh token from cache
+ - Call token endpoint with refresh token grant
+ - Parse `TokenResponse` into `MsalTokenResponse`
+5. **Cache Update**: Write new tokens to `TokenCache`
+6. **Response**: Return `AuthenticationResult` to caller
+
+#### Interactive Token Acquisition
+1. **Request Initiation**: Application calls `app.AcquireTokenInteractive(scopes).ExecuteAsync()`
+2. **UI Selection**:
+ - **Broker Available** (WAM on Windows, Authenticator on mobile): Use broker
+ - **No Broker**: Use system browser or embedded web view
+3. **Authorization**: User authenticates and consents
+4. **Authorization Code**: Redirect URI receives authorization code
+5. **Token Exchange**: Exchange code for tokens at token endpoint
+6. **Cache Update**: Store tokens in `TokenCache`
+7. **Response**: Return `AuthenticationResult` with access token, ID token, account info
+
+#### Client Credentials Flow (Confidential Client)
+1. **Request Initiation**: `app.AcquireTokenForClient(scopes).ExecuteAsync()`
+2. **App Cache Lookup** (`AppTokenCacheInternal`): Check for cached app token
+3. **Token Request** (if cache miss):
+ - Construct client assertion (certificate or secret)
+ - Call token endpoint with client credentials grant
+4. **Cache Update**: Store app token in `AppTokenCacheInternal`
+5. **Response**: Return `AuthenticationResult`
+
+### Key Design Patterns
+
+#### 1. Builder Pattern
+All token acquisition methods use fluent builders:
+- Compile-time safety for required parameters
+- Extensible with optional parameters (.With* methods)
+- Clear API surface
+
+#### 2. Internal Abstractions
+- Extensive use of `internal` namespaces to hide implementation details
+- Clean separation between public API and internal logic
+- Prevents external dependencies on internal types
+
+#### 3. Dependency Injection
+- `ServiceBundle` acts as a lightweight DI container
+- Platform-specific implementations injected via `IPlatformProxy`
+- Testability through interface-based design
+
+#### 4. Async/Await Throughout
+- All I/O operations are async
+- Proper cancellation token support
+- No blocking calls in async code paths
+
+#### 5. Caching Strategy
+- Layered caching: in-memory → custom serialization → platform-specific storage
+- Read-through cache pattern
+- Atomic cache operations with optional synchronization
+
+#### 6. Telemetry & Diagnostics
+- Per-request correlation IDs
+- Comprehensive logging with PII controls
+- Performance metrics for every operation
+- Integration with Azure Monitor via MATS
+
+### Platform Support Matrix
+
+| Platform | Target Framework(s) | Broker Support | WebView Support | Notes |
+|----------|---------------------|----------------|-----------------|-------|
+| Windows Desktop | .NET Framework 4.6.2+, .NET Core 3.1+, .NET 6+ | WAM (Win10+) | Embedded, System | Full feature support |
+| Linux | .NET Core 3.1+, .NET 6+ | No | System only | Experimental broker (preview) |
+| Mac | .NET Core 3.1+, .NET 6+ | Company Portal | System only | Requires Company Portal |
+| iOS | Xamarin.iOS, .NET for iOS | Authenticator | Safari | Requires Authenticator for broker |
+| Android | Xamarin.Android, .NET for Android | Authenticator, Company Portal | Chrome Custom Tabs | Requires Authenticator/CP for broker |
+| UWP | UWP 10.0.17763+ | WAM | Embedded | Windows 10+ only |
+
+### Token Types & Flows
+
+#### Public Client Flows
+- **Interactive**: User-driven browser/broker authentication
+- **Device Code**: For browserless devices
+- **Integrated Windows Auth** (Deprecated): Kerberos-based auth
+- **Username/Password** (Deprecated): Resource Owner Password Credentials
+
+#### Confidential Client Flows
+- **Client Credentials**: Service-to-service authentication
+- **Authorization Code**: Web app authentication
+- **On-Behalf-Of (OBO)**: Middle-tier service calling downstream API
+- **Long-Running OBO**: Background processing scenarios
+
+#### Token Types
+- **Bearer Tokens**: Standard OAuth2 access tokens (default)
+- **Proof-of-Possession (PoP)**: Cryptographically bound tokens
+- **SSH Certificates**: Special token type for SSH scenarios
+- **mTLS Tokens**: Certificate-bound tokens (experimental)
+
+### Thread Safety & Concurrency
+- Token cache operations are thread-safe
+- `OptionalSemaphoreSlim` provides configurable synchronization
+- Confidential clients support optimistic concurrency (disable cache sync)
+- Atomic cache updates prevent race conditions
+
+### Backward Compatibility
+- ADAL (v2/v3) cache format support for migration
+- Deprecated APIs marked with `[Obsolete]`
+- Semantic versioning for public API changes
+
+## Implementation Standards (when implementing)
+- Keep diffs minimal and focused.
+- Reuse existing helpers and patterns.
+- Avoid broad refactors and unrelated formatting.
+- Don’t create extra `.md` files unless requested.
+
+## Code Style & Consistency Checklist
+
+Before finishing:
+- [ ] Does this match the surrounding code style (naming, structure, patterns)?
+- [ ] Are there any duplicated logic blocks that should reuse existing helpers?
+- [ ] Are changes localized to the requested behavior?
+- [ ] Are error messages consistent with existing ones?
+- [ ] Are logs/telemetry consistent (or avoided if not used elsewhere)?
+- [ ] Are types/interfaces (if present) aligned with existing conventions?
+- [ ] Are tests added/updated appropriately?
+
+---
+
+## Documentation Rules
+
+- Do **not** create new `.md` files unless explicitly requested.
+- If documentation must be updated:
+ 1. Prefer updating an existing doc where similar topics are documented.
+ 2. Keep it short and practical (usage + example).
+ 3. Avoid long design writeups.
+
+---
+
+## When Requirements Are Ambiguous
+
+Do not pause the implementation to ask multiple questions.
+Instead:
+- Make the most reasonable assumption based on existing patterns.
+- State the assumption clearly in the plan.
+- Implement in a way that is easy to adjust.
+
+---
+
+## Implementation Standard
+
+### Good changes look like:
+- Small, targeted diffs
+- Reuse of existing functions/utilities
+- Consistent behavior with adjacent modules
+- Minimal surface-area impact
+- Tests for new/changed behavior
+
+### Avoid:
+- Broad refactors
+- Unrelated formatting changes
+- New dependencies for minor features
+- Creating new docs for internal notes
+
+---
+
+## Commit / PR Notes (keep concise)
+When summarizing work, follow:
+- **Problem:** what was broken/missing
+- **Solution:** what you changed + why
+- **Tests:** what you ran
+
+Example:
+- Problem: endpoint returned 500 on empty payload
+- Solution: validate payload, reuse existing validator, return 400 with consistent error shape
+- Tests: npm test (unit), manual curl validation
diff --git a/global.json b/global.json
index d456229706..37bf7f81e0 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.418",
+ "version": "10.0.101",
"rollForward": "latestFeature"
}
}
diff --git a/newjson.json b/newjson.json
new file mode 100644
index 0000000000..8cf3ccce00
--- /dev/null
+++ b/newjson.json
@@ -0,0 +1,34 @@
+{
+ "aud": "https://graph.microsoft.com",
+ "iss": "https://sts.windows.net/10c419d4-4a50-45b2-aa4e-919fb84df24f/",
+ "iat": 1772116820,
+ "nbf": 1772116820,
+ "exp": 1772120720,
+ "aio": "k2ZgYNB+d4klYinL6VXezG8Kv0XOv6CfvZtv7csMlcCaaNU/3xIA",
+ "app_displayname": "agent identity1",
+ "appid": "ab18ca07-d139-4840-8b3b-4be9610c6ed5",
+ "appidacr": "2",
+ "idp": "https://sts.windows.net/10c419d4-4a50-45b2-aa4e-919fb84df24f/",
+ "idtyp": "app",
+ "oid": "ab18ca07-d139-4840-8b3b-4be9610c6ed5",
+ "rh": "1.AXEB1BnEEFBKskWqTpGfuE3yTwMAAAAAAAAAwAAAAAAAAAAAAABxAQ.",
+ "roles": [
+ "Application.Read.All"
+ ],
+ "sub": "ab18ca07-d139-4840-8b3b-4be9610c6ed5",
+ "tenant_region_scope": "NA",
+ "tid": "10c419d4-4a50-45b2-aa4e-919fb84df24f",
+ "uti": "IYGgzr8M10aNw3nANQ54AA",
+ "ver": "1.0",
+ "wids": [
+ "0997a1d0-0d1d-4acb-b408-d5ca73121e90"
+ ],
+ "xms_act_fct": "3 11 9",
+ "xms_ftd": "sw2pKG6aFqnm6r73WSeCHfTFQuXdVSMGGhwt6ZXNZc0BdXNlYXN0LWRzbXM",
+ "xms_idrel": "7 24",
+ "xms_par_app_azp": "aab5089d-e764-47e3-9f28-cc11c2513821",
+ "xms_rd": "0.42LjYBJi-sgkJMLBLiSwXPdScKJQr_NWO_4eqyYDb6Aop5BACO8aMca9x723774btjW2UgsoyiEkwMwAAQegNFCUW0jg2WZn59bGuydzGyIX-4RWM0jxcXAJcRmamxsZGppZGFoCAA",
+ "xms_sub_fct": "3 11 9",
+ "xms_tcdt": 1753717349,
+ "xms_tnt_fct": "3 12"
+}
\ No newline at end of file
diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForAgentOnBehalfOfUserParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForAgentOnBehalfOfUserParameterBuilder.cs
new file mode 100644
index 0000000000..62889e2aa0
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForAgentOnBehalfOfUserParameterBuilder.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Identity.Client
+{
+ ///
+ /// Builder for acquiring a user-delegated token for an agent identity, acting on behalf of a specified user.
+ /// Use
+ /// to create this builder.
+ ///
+ ///
+ /// This flow internally:
+ ///
+ /// - Obtains an FMI credential (FIC) from the token exchange endpoint using the CCA's credential.
+ /// - Obtains a User Federated Identity Credential (User FIC) for the agent.
+ /// - Exchanges the User FIC for a user-delegated token via the user_fic grant type.
+ ///
+ /// After a successful acquisition, you can use
+ /// with the returned account for subsequent cached token lookups.
+ ///
+#if !SUPPORTS_CONFIDENTIAL_CLIENT
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+#endif
+ public sealed class AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+ {
+ private readonly ConfidentialClientApplication _app;
+ private readonly string _agentId;
+ private readonly IEnumerable _scopes;
+ private readonly string _userPrincipalName;
+ private bool _forceRefresh;
+ private Guid? _correlationId;
+
+ internal AcquireTokenForAgentOnBehalfOfUserParameterBuilder(
+ ConfidentialClientApplication app,
+ string agentId,
+ IEnumerable scopes,
+ string userPrincipalName)
+ {
+ _app = app ?? throw new ArgumentNullException(nameof(app));
+ _agentId = agentId ?? throw new ArgumentNullException(nameof(agentId));
+ _scopes = scopes ?? throw new ArgumentNullException(nameof(scopes));
+ _userPrincipalName = userPrincipalName ?? throw new ArgumentNullException(nameof(userPrincipalName));
+ }
+
+ ///
+ /// Forces MSAL to refresh the token from the identity provider, bypassing the cache.
+ ///
+ /// If true, ignore any cached tokens and request a new token.
+ /// The builder, for fluent chaining.
+ public AcquireTokenForAgentOnBehalfOfUserParameterBuilder WithForceRefresh(bool forceRefresh)
+ {
+ _forceRefresh = forceRefresh;
+ return this;
+ }
+
+ ///
+ /// Sets a correlation ID for telemetry and diagnostics.
+ ///
+ /// A GUID to correlate requests across services.
+ /// The builder, for fluent chaining.
+ public AcquireTokenForAgentOnBehalfOfUserParameterBuilder WithCorrelationId(Guid correlationId)
+ {
+ _correlationId = correlationId;
+ return this;
+ }
+
+ ///
+ /// Executes the token acquisition asynchronously.
+ ///
+ /// Cancellation token to cancel the operation.
+ /// An containing the requested user-delegated token.
+ public Task ExecuteAsync(CancellationToken cancellationToken = default)
+ {
+ return _app.ExecuteAgentOnBehalfOfUserAsync(
+ _agentId, _scopes, _userPrincipalName, _forceRefresh, _correlationId, cancellationToken);
+ }
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForAgentParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForAgentParameterBuilder.cs
new file mode 100644
index 0000000000..88595c7dea
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForAgentParameterBuilder.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Identity.Client
+{
+ ///
+ /// Builder for acquiring an app-only token for an agent identity via a
+ /// .
+ /// Use
+ /// to create this builder.
+ ///
+ ///
+ /// This flow internally:
+ ///
+ /// - Obtains an FMI credential (FIC) from the token exchange endpoint using the CCA's credential.
+ /// - Uses the FIC as a client assertion to acquire a token for the requested scopes.
+ ///
+ ///
+#if !SUPPORTS_CONFIDENTIAL_CLIENT
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+#endif
+ public sealed class AcquireTokenForAgentParameterBuilder
+ {
+ private readonly ConfidentialClientApplication _app;
+ private readonly string _agentId;
+ private readonly IEnumerable _scopes;
+ private bool _forceRefresh;
+ private Guid? _correlationId;
+
+ internal AcquireTokenForAgentParameterBuilder(
+ ConfidentialClientApplication app, string agentId, IEnumerable scopes)
+ {
+ _app = app ?? throw new ArgumentNullException(nameof(app));
+ _agentId = agentId ?? throw new ArgumentNullException(nameof(agentId));
+ _scopes = scopes ?? throw new ArgumentNullException(nameof(scopes));
+ }
+
+ ///
+ /// Forces MSAL to refresh the token from the identity provider, bypassing the cache.
+ ///
+ /// If true, ignore any cached tokens and request a new token.
+ /// The builder, for fluent chaining.
+ public AcquireTokenForAgentParameterBuilder WithForceRefresh(bool forceRefresh)
+ {
+ _forceRefresh = forceRefresh;
+ return this;
+ }
+
+ ///
+ /// Sets a correlation ID for telemetry and diagnostics.
+ ///
+ /// A GUID to correlate requests across services.
+ /// The builder, for fluent chaining.
+ public AcquireTokenForAgentParameterBuilder WithCorrelationId(Guid correlationId)
+ {
+ _correlationId = correlationId;
+ return this;
+ }
+
+ ///
+ /// Executes the token acquisition asynchronously.
+ ///
+ /// Cancellation token to cancel the operation.
+ /// An containing the requested token.
+ public Task ExecuteAsync(CancellationToken cancellationToken = default)
+ {
+ return _app.ExecuteAgentTokenAcquisitionAsync(
+ _agentId, _scopes, _forceRefresh, _correlationId, cancellationToken);
+ }
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs
index 4364ad143f..25d726c70d 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs
@@ -126,6 +126,8 @@ public string ClientVersion
public bool IsPublicClient => !IsConfidentialClient && !IsManagedIdentity;
public string CertificateIdToAssociateWithToken { get; set; }
+ public string FederatedCredentialAudience { get; internal set; } = "api://AzureADTokenExchange/.default";
+
public Func> AppTokenProvider;
internal IRetryPolicyFactory RetryPolicyFactory { get; set; }
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
index 4c2f86198b..dd8e43fc6e 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
@@ -454,6 +454,23 @@ internal ConfidentialClientApplicationBuilder WithAppTokenCacheInternalForTest(I
return this;
}
+ ///
+ /// Sets the audience URL used when acquiring Federated Identity Credentials (FIC) for agentic flows.
+ /// Defaults to api://AzureADTokenExchange/.default. Override for airgapped or sovereign clouds.
+ ///
+ /// The FIC audience URL.
+ /// The builder to chain the .With methods.
+ public ConfidentialClientApplicationBuilder WithFederatedCredentialAudience(string audience)
+ {
+ if (string.IsNullOrWhiteSpace(audience))
+ {
+ throw new ArgumentNullException(nameof(audience));
+ }
+
+ Config.FederatedCredentialAudience = audience;
+ return this;
+ }
+
///
internal override void Validate()
{
diff --git a/src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs b/src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs
index cea2e10ccb..85d54dc523 100644
--- a/src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs
+++ b/src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
@@ -9,6 +10,7 @@
using Microsoft.Identity.Client.ApiConfig.Executors;
using Microsoft.Identity.Client.ApiConfig.Parameters;
using Microsoft.Identity.Client.Core;
+using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Internal.Requests;
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;
@@ -204,6 +206,161 @@ AcquireTokenByRefreshTokenParameterBuilder IByRefreshToken.AcquireTokenByRefresh
// Stores all app tokens
internal ITokenCacheInternal AppTokenCacheInternal { get; }
+ // Cache of agent CCAs keyed by agent identity, used by AcquireTokenForAgent
+ private readonly ConcurrentDictionary _agentCcaCache =
+ new ConcurrentDictionary();
+
+ ///
+ public AcquireTokenForAgentParameterBuilder AcquireTokenForAgent(
+ string agentId, IEnumerable scopes)
+ {
+ if (string.IsNullOrEmpty(agentId))
+ throw new ArgumentNullException(nameof(agentId));
+ if (scopes == null)
+ throw new ArgumentNullException(nameof(scopes));
+
+ return new AcquireTokenForAgentParameterBuilder(this, agentId, scopes);
+ }
+
+ ///
+ public AcquireTokenForAgentOnBehalfOfUserParameterBuilder AcquireTokenForAgentOnBehalfOfUser(
+ string agentId, IEnumerable scopes, string userPrincipalName)
+ {
+ if (string.IsNullOrEmpty(agentId))
+ throw new ArgumentNullException(nameof(agentId));
+ if (scopes == null)
+ throw new ArgumentNullException(nameof(scopes));
+ if (string.IsNullOrEmpty(userPrincipalName))
+ throw new ArgumentNullException(nameof(userPrincipalName));
+
+ return new AcquireTokenForAgentOnBehalfOfUserParameterBuilder(this, agentId, scopes, userPrincipalName);
+ }
+
+ ///
+ /// Executes the two-step agentic token acquisition:
+ /// 1. Gets FIC from the token exchange endpoint using this CCA's credential.
+ /// 2. Uses the FIC as a client assertion in an agent CCA to acquire the target token.
+ ///
+ internal async Task ExecuteAgentTokenAcquisitionAsync(
+ string agentId,
+ IEnumerable scopes,
+ bool forceRefresh,
+ Guid? correlationId,
+ CancellationToken cancellationToken)
+ {
+ var agentCca = GetOrCreateAgentCca(agentId);
+
+ var builder = agentCca.AcquireTokenForClient(scopes);
+
+ if (forceRefresh)
+ builder = builder.WithForceRefresh(true);
+
+ if (correlationId.HasValue)
+ builder = builder.WithCorrelationId(correlationId.Value);
+
+ return await builder
+ .ExecuteAsync(cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Executes the three-step agentic user-delegated token acquisition:
+ /// 1. Gets FIC from the token exchange endpoint using this CCA's credential (via the agent CCA's assertion).
+ /// 2. Gets a User FIC via AcquireTokenForClient + WithFmiPathForClientAssertion on the agent CCA.
+ /// 3. Uses the User FIC in a user_fic grant type request to get a user-delegated token.
+ ///
+ internal async Task ExecuteAgentOnBehalfOfUserAsync(
+ string agentId,
+ IEnumerable scopes,
+ string userPrincipalName,
+ bool forceRefresh,
+ Guid? correlationId,
+ CancellationToken cancellationToken)
+ {
+ var agentCca = GetOrCreateAgentCca(agentId);
+ var ficAudience = ServiceBundle.Config.FederatedCredentialAudience;
+
+ // Step 1 + 2: Get User FIC.
+ // The agent CCA's assertion callback already handles step 1 (getting the app FIC
+ // from the platform CCA). WithFmiPathForClientAssertion passes the agentId to
+ // that callback. The result is a User FIC token.
+ var userFicResult = await agentCca.AcquireTokenForClient(new[] { ficAudience })
+ .WithFmiPathForClientAssertion(agentId)
+ .ExecuteAsync(cancellationToken)
+ .ConfigureAwait(false);
+
+ string userFicAssertion = userFicResult.AccessToken;
+
+ // Step 3: Exchange the User FIC for a user-delegated token using user_fic grant.
+ var usernamePasswordBuilder = ((IByUsernameAndPassword)agentCca)
+ .AcquireTokenByUsernamePassword(scopes, userPrincipalName, "no_password");
+
+ if (correlationId.HasValue)
+ usernamePasswordBuilder = usernamePasswordBuilder.WithCorrelationId(correlationId.Value);
+
+ return await usernamePasswordBuilder
+ .OnBeforeTokenRequest(async (request) =>
+ {
+ request.BodyParameters["user_federated_identity_credential"] = userFicAssertion;
+ request.BodyParameters["grant_type"] = "user_fic";
+
+ // Remove the dummy password — not needed for user_fic grant
+ request.BodyParameters.Remove("password");
+
+ // Remove client_secret if it's the default placeholder
+ if (request.BodyParameters.TryGetValue("client_secret", out var secret)
+ && secret.Equals("default", StringComparison.OrdinalIgnoreCase))
+ {
+ request.BodyParameters.Remove("client_secret");
+ }
+
+ await Task.CompletedTask.ConfigureAwait(false);
+ })
+ .ExecuteAsync(cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ private IConfidentialClientApplication GetOrCreateAgentCca(string agentId)
+ {
+ return _agentCcaCache.GetOrAdd(agentId, id =>
+ {
+ var config = ServiceBundle.Config;
+ var authorityUri = config.Authority.AuthorityInfo.CanonicalAuthority.ToString();
+ var ficAudience = config.FederatedCredentialAudience;
+
+ // The agent CCA uses a client assertion that fetches the FIC on demand
+ // from *this* (platform) CCA.
+ var platformCca = this as IConfidentialClientApplication;
+
+ var agentBuilder = ConfidentialClientApplicationBuilder
+ .Create(id)
+ .WithAuthority(authorityUri)
+ .WithExperimentalFeatures(true)
+ .WithClientAssertion(async (AssertionRequestOptions options) =>
+ {
+ string fmiPath = options.ClientAssertionFmiPath ?? id;
+ var result = await platformCca.AcquireTokenForClient(new[] { ficAudience })
+ .WithFmiPath(fmiPath)
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+ return result.AccessToken;
+ });
+
+ if (config.AccessorOptions != null)
+ agentBuilder = agentBuilder.WithCacheOptions(config.AccessorOptions);
+ if (config.HttpClientFactory != null)
+ agentBuilder = agentBuilder.WithHttpClientFactory(config.HttpClientFactory);
+ if (config.IdentityLogger != null)
+ agentBuilder = agentBuilder.WithLogging(config.IdentityLogger, config.EnablePiiLogging);
+ if (config.HttpManager != null)
+ agentBuilder = agentBuilder.WithHttpManager(config.HttpManager);
+ if (!config.IsInstanceDiscoveryEnabled)
+ agentBuilder = agentBuilder.WithInstanceDiscovery(false);
+
+ return agentBuilder.Build();
+ });
+ }
+
internal override async Task CreateRequestParametersAsync(
AcquireTokenCommonParameters commonParameters,
RequestContext requestContext,
diff --git a/src/client/Microsoft.Identity.Client/IConfidentialClientApplication.cs b/src/client/Microsoft.Identity.Client/IConfidentialClientApplication.cs
index 90f6e7791d..46c4c752f1 100644
--- a/src/client/Microsoft.Identity.Client/IConfidentialClientApplication.cs
+++ b/src/client/Microsoft.Identity.Client/IConfidentialClientApplication.cs
@@ -86,6 +86,38 @@ AcquireTokenByAuthorizationCodeParameterBuilder AcquireTokenByAuthorizationCode(
/// URL of the authorization endpoint with the specified parameters.
GetAuthorizationRequestUrlParameterBuilder GetAuthorizationRequestUrl(IEnumerable scopes);
+ ///
+ /// Acquires an app-only token for an agent identity using Federated Managed Identity (FMI).
+ /// Internally, this method:
+ ///
+ /// - Obtains an FMI credential (FIC) from the token exchange endpoint using the current CCA's credential.
+ /// - Uses the FIC as a client assertion to acquire a token for the requested scopes on behalf of the agent.
+ ///
+ ///
+ /// The FMI path or client ID of the agent identity.
+ /// Scopes requested to access a protected API, e.g., https://graph.microsoft.com/.default.
+ /// A builder enabling you to add optional parameters before executing the token request.
+ AcquireTokenForAgentParameterBuilder AcquireTokenForAgent(string agentId, IEnumerable scopes);
+
+ ///
+ /// Acquires a user-delegated token for an agent identity, acting on behalf of the specified user,
+ /// using Federated Managed Identity (FMI) and the user_fic grant type.
+ /// Internally, this method:
+ ///
+ /// - Obtains an FMI credential (FIC) from the token exchange endpoint using the current CCA's credential.
+ /// - Obtains a User FIC for the agent identity.
+ /// - Exchanges the User FIC for a user-delegated token via the user_fic grant type.
+ ///
+ /// After a successful acquisition, use
+ /// with the returned account for subsequent cached token lookups.
+ ///
+ /// The FMI path or client ID of the agent identity.
+ /// Scopes requested to access a protected API, e.g., https://graph.microsoft.com/.default.
+ /// The UPN of the user on whose behalf the agent is acting, e.g., user@contoso.com.
+ /// A builder enabling you to add optional parameters before executing the token request.
+ AcquireTokenForAgentOnBehalfOfUserParameterBuilder AcquireTokenForAgentOnBehalfOfUser(
+ string agentId, IEnumerable scopes, string userPrincipalName);
+
///
/// In confidential client apps use instead.
///
diff --git a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
index 7c08fe3762..9705dd06cc 100644
--- a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
+++ b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
@@ -80,6 +80,7 @@
+
@@ -162,4 +163,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
index 5f1ab1006d..a2e058bff3 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
@@ -1 +1,14 @@
-Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.cancellationToken.get -> System.Threading.CancellationToken
\ No newline at end of file
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.cancellationToken.get -> System.Threading.CancellationToken
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithFederatedCredentialAudience(string audience) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
index 49f0d092b6..a2e058bff3 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
@@ -1 +1,14 @@
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.cancellationToken.get -> System.Threading.CancellationToken
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithFederatedCredentialAudience(string audience) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
index 49f0d092b6..a2e058bff3 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
@@ -1 +1,14 @@
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.cancellationToken.get -> System.Threading.CancellationToken
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithFederatedCredentialAudience(string audience) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
index 49f0d092b6..a2e058bff3 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
@@ -1 +1,14 @@
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.cancellationToken.get -> System.Threading.CancellationToken
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithFederatedCredentialAudience(string audience) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
index 49f0d092b6..a2e058bff3 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
@@ -1 +1,14 @@
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.cancellationToken.get -> System.Threading.CancellationToken
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithFederatedCredentialAudience(string audience) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
index 49f0d092b6..a2e058bff3 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1 +1,14 @@
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.ExecuteAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithCorrelationId(System.Guid correlationId) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder.WithForceRefresh(bool forceRefresh) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.cancellationToken.get -> System.Threading.CancellationToken
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithFederatedCredentialAudience(string audience) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgent(string agentId, System.Collections.Generic.IEnumerable scopes) -> Microsoft.Identity.Client.AcquireTokenForAgentParameterBuilder
+Microsoft.Identity.Client.IConfidentialClientApplication.AcquireTokenForAgentOnBehalfOfUser(string agentId, System.Collections.Generic.IEnumerable scopes, string userPrincipalName) -> Microsoft.Identity.Client.AcquireTokenForAgentOnBehalfOfUserParameterBuilder
diff --git a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/AgenticCcaE2ETest.cs b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/AgenticCcaE2ETest.cs
new file mode 100644
index 0000000000..444a9c1e37
--- /dev/null
+++ b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/AgenticCcaE2ETest.cs
@@ -0,0 +1,324 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.Extensibility;
+using Microsoft.Identity.Test.LabInfrastructure;
+using Microsoft.Identity.Test.Unit;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.Identity.Test.Integration.HeadlessTests
+{
+ ///
+ /// E2E integration tests that validate the new CCA-based agentic API.
+ /// Compare with (the original baseline) to see the difference:
+ ///
+ /// OLD (Agentic.cs baseline):
+ /// - Manually creates a platform CCA + assertion callback to get FIC
+ /// - Creates a second CCA with the agent identity + FIC as client assertion
+ /// - Calls AcquireTokenForClient on the agent CCA
+ ///
+ /// NEW (this file):
+ /// - Creates a single platform CCA with certificate
+ /// - Calls cca.AcquireTokenForAgent(agentId, scopes) � all FIC orchestration is internal
+ ///
+ [TestClass]
+ public class AgenticCcaE2ETest
+ {
+ // Same constants as Agentic.cs so the flows are directly comparable
+ private const string PlatformClientId = "aab5089d-e764-47e3-9f28-cc11c2513821"; // platform (host) app
+ private const string TenantId = "10c419d4-4a50-45b2-aa4e-919fb84df24f";
+ private const string AgentIdentity = "ab18ca07-d139-4840-8b3b-4be9610c6ed5";
+ private const string UserUpn = "agentuser1@id4slab1.onmicrosoft.com";
+ private const string TokenExchangeUrl = "api://AzureADTokenExchange/.default";
+ private const string Scope = "https://graph.microsoft.com/.default";
+
+ #region Case 1a � Certificate credential via AcquireTokenForAgent
+
+ ///
+ /// Mirrors but uses the new
+ /// API.
+ ///
+ /// Instead of manually wiring a platform CCA ? FIC ? agent CCA ? token,
+ /// the caller just does:
+ /// cca.AcquireTokenForAgent(agentId, scopes).ExecuteAsync()
+ ///
+ [TestMethod]
+ public async Task AgentGetsAppTokenWithCertificate_ViaAcquireTokenForAgentTest()
+ {
+ X509Certificate2 cert = CertificateHelper.FindCertificateByName(TestConstants.AutomationTestCertName);
+
+ // Single platform CCA � no need for a separate agent CCA
+ var cca = ConfidentialClientApplicationBuilder
+ .Create(PlatformClientId)
+ .WithAuthority("https://login.microsoftonline.com/", TenantId)
+ .WithCertificate(cert, sendX5C: true)
+ .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
+ .WithExperimentalFeatures(true)
+ .Build();
+
+ // One call does the full two-step flow internally:
+ // 1. AcquireTokenForClient(api://AzureADTokenExchange/.default).WithFmiPath(agentId)
+ // 2. Creates an internal agent CCA that uses the FIC as client assertion
+ // 3. AcquireTokenForClient(scopes) on the agent CCA
+ var result = await cca
+ .AcquireTokenForAgent(AgentIdentity, [Scope])
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+
+ Assert.IsNotNull(result, "AuthenticationResult should not be null");
+ Assert.IsNotNull(result.AccessToken, "AccessToken should not be null");
+ Assert.IsTrue(result.ExpiresOn > DateTimeOffset.UtcNow, "Token should not be expired");
+
+ Trace.WriteLine($"[CCA Case 1a] App token acquired from: {result.AuthenticationResultMetadata.TokenSource}");
+ }
+
+ #endregion
+
+ #region Case 1b � Client assertion credential via AcquireTokenForAgent
+
+ ///
+ /// Same as Case 1a but the platform CCA authenticates with a
+ /// WithClientAssertion callback instead of WithCertificate.
+ ///
+ /// The callback uses to get
+ /// ClientID and TokenEndpoint at runtime � no need to
+ /// hardcode the audience or client ID in the closure.
+ ///
+ /// AcquireTokenForAgent works identically regardless of how the
+ /// platform CCA authenticates.
+ ///
+ [TestMethod]
+ public async Task AgentGetsAppTokenWithAssertion_ViaAcquireTokenForAgentTest()
+ {
+ X509Certificate2 cert = CertificateHelper.FindCertificateByName(TestConstants.AutomationTestCertName);
+
+ // Platform CCA configured with WithClientAssertion � no WithCertificate here.
+ // The callback only captures the cert; everything else comes from
+ // AssertionRequestOptions at runtime.
+ var cca = ConfidentialClientApplicationBuilder
+ .Create(PlatformClientId)
+ .WithAuthority($"https://login.microsoftonline.com/{TenantId}")
+ .WithExperimentalFeatures(true)
+ .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
+ .WithClientAssertion((AssertionRequestOptions opts) =>
+ {
+ // opts.ClientID = the platform app's client ID (issuer/subject)
+ // opts.TokenEndpoint = the exact token endpoint URL (audience)
+ // No hardcoded strings needed � MSAL tells us everything.
+ string jwt = CreateSignedJwt(opts.ClientID, opts.TokenEndpoint, cert);
+ return Task.FromResult(jwt);
+ })
+ .Build();
+
+ // Same single call as Case 1a � proves the new API is
+ // credential-agnostic.
+ var result = await cca
+ .AcquireTokenForAgent(AgentIdentity, [Scope])
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+
+ Assert.IsNotNull(result, "AuthenticationResult should not be null");
+ Assert.IsNotNull(result.AccessToken, "AccessToken should not be null");
+ Assert.IsTrue(result.ExpiresOn > DateTimeOffset.UtcNow, "Token should not be expired");
+
+ Trace.WriteLine($"[CCA Case 1b] App token acquired from: {result.AuthenticationResultMetadata.TokenSource}");
+ }
+
+ #endregion
+
+ #region E2E � Agent acts on behalf of a user (user_fic grant, same as baseline)
+
+ ///
+ /// Mirrors exactly.
+ /// The user-delegated flow still uses the CCA + OnBeforeTokenRequest pattern.
+ ///
+ [TestMethod]
+ public async Task AgentUserIdentityGetsTokenForGraph_ViaOnBehalfOfUserTest()
+ {
+ var cca = ConfidentialClientApplicationBuilder
+ .Create(AgentIdentity)
+ .WithAuthority("https://login.microsoftonline.com/", TenantId)
+ .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
+ .WithExperimentalFeatures(true)
+ .WithExtraQueryParameters(new Dictionary { { "slice", ("first", false) } })
+ .WithClientAssertion((AssertionRequestOptions _) => GetAppCredentialAsync(AgentIdentity))
+ .Build();
+
+ var result = await (cca as IByUsernameAndPassword).AcquireTokenByUsernamePassword([Scope], UserUpn, "no_password")
+ .OnBeforeTokenRequest(
+ async (request) =>
+ {
+ string userFicAssertion = await GetUserFic().ConfigureAwait(false);
+ request.BodyParameters["user_federated_identity_credential"] = userFicAssertion;
+ request.BodyParameters["grant_type"] = "user_fic";
+
+ // remove the password
+ request.BodyParameters.Remove("password");
+
+ if (request.BodyParameters.TryGetValue("client_secret", out var secret)
+ && secret.Equals("default", StringComparison.OrdinalIgnoreCase))
+ {
+ request.BodyParameters.Remove("client_secret");
+ }
+ }
+ )
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+
+ Assert.IsNotNull(result, "AuthenticationResult should not be null");
+ Assert.IsNotNull(result.AccessToken, "AccessToken should not be null");
+ Assert.IsNotNull(result.Account, "Account should not be null after user flow");
+
+ Trace.WriteLine($"[CCA E2E user] User token acquired from: {result.AuthenticationResultMetadata.TokenSource}");
+
+ // Validate silent (cached) acquisition
+ IAccount account = await cca.GetAccountAsync(result.Account.HomeAccountId.Identifier).ConfigureAwait(false);
+ Assert.IsNotNull(account, "Account retrieved from cache should not be null");
+
+ var result2 = await cca.AcquireTokenSilent([Scope], account).ExecuteAsync().ConfigureAwait(false);
+ Assert.IsTrue(result2.AuthenticationResultMetadata.TokenSource == TokenSource.Cache, "Token should be from cache");
+
+ Trace.WriteLine($"[CCA E2E user] Silent token acquired from: {result2.AuthenticationResultMetadata.TokenSource}");
+ }
+
+ #endregion
+
+ #region E2E � Agent on behalf of user via AcquireTokenForAgentOnBehalfOfUser (NEW simplified API)
+
+ ///
+ /// Mirrors but uses the new
+ /// API.
+ ///
+ /// COMPARE � OLD (Agentic.cs baseline, ~30 lines of ceremony):
+ /// 1. Create CCA with agent identity + assertion callback
+ /// 2. Cast to IByUsernameAndPassword
+ /// 3. Call AcquireTokenByUsernamePassword with dummy password
+ /// 4. OnBeforeTokenRequest ? manually get User FIC, rewrite grant_type, strip password, etc.
+ ///
+ /// NEW (this test, 1 call):
+ /// cca.AcquireTokenForAgentOnBehalfOfUser(agentId, scopes, upn).ExecuteAsync()
+ ///
+ /// All the User FIC acquisition, grant_type rewriting, and password stripping
+ /// is handled internally by the CCA.
+ ///
+ [TestMethod]
+ public async Task AgentUserIdentityGetsToken_ViaAcquireTokenForAgentOnBehalfOfUserTest()
+ {
+ X509Certificate2 cert = CertificateHelper.FindCertificateByName(TestConstants.AutomationTestCertName);
+
+ // Single platform CCA with certificate � same as Case 1a
+ var cca = ConfidentialClientApplicationBuilder
+ .Create(PlatformClientId)
+ .WithAuthority("https://login.microsoftonline.com/", TenantId)
+ .WithCertificate(cert, sendX5C: true)
+ .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
+ .WithExperimentalFeatures(true)
+ .Build();
+
+ // One call hides the entire user_fic complexity:
+ // 1. Gets FIC from platform cert ? agent CCA (cached)
+ // 2. Gets User FIC via agent CCA + WithFmiPathForClientAssertion
+ // 3. Rewrites the token request to user_fic grant with the User FIC
+ var result = await cca
+ .AcquireTokenForAgentOnBehalfOfUser(AgentIdentity, [Scope], UserUpn)
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+
+ Assert.IsNotNull(result, "AuthenticationResult should not be null");
+ Assert.IsNotNull(result.AccessToken, "AccessToken should not be null");
+ Assert.IsNotNull(result.Account, "Account should not be null after user flow");
+
+ Trace.WriteLine($"[CCA new user API] User token acquired from: {result.AuthenticationResultMetadata.TokenSource}");
+
+ // Note: Silent token acquisition via cca.AcquireTokenSilent is not available
+ // here because the user-delegated token lives in the internal agent CCA's cache,
+ // not the platform CCA's cache. Calling AcquireTokenForAgentOnBehalfOfUser again
+ // will benefit from caching at the internal agent CCA level.
+ }
+
+ #endregion
+
+ #region Helpers (same as Agentic.cs baseline)
+
+ private static async Task GetAppCredentialAsync(string fmiPath)
+ {
+ Assert.IsNotNull(fmiPath, "fmiPath cannot be null");
+ X509Certificate2 cert = CertificateHelper.FindCertificateByName(TestConstants.AutomationTestCertName);
+
+ var cca1 = ConfidentialClientApplicationBuilder
+ .Create(PlatformClientId)
+ .WithAuthority("https://login.microsoftonline.com/", TenantId)
+ .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
+ .WithExperimentalFeatures(true)
+ .WithCertificate(cert, sendX5C: true)
+ .Build();
+
+ var result = await cca1.AcquireTokenForClient([TokenExchangeUrl])
+ .WithFmiPath(fmiPath)
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+
+ Trace.WriteLine($"FMI app credential from: {result.AuthenticationResultMetadata.TokenSource}");
+
+ return result.AccessToken;
+ }
+
+ private static async Task GetUserFic()
+ {
+ var cca1 = ConfidentialClientApplicationBuilder
+ .Create(AgentIdentity)
+ .WithAuthority("https://login.microsoftonline.com/", TenantId)
+ .WithExperimentalFeatures(true)
+ .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
+ .WithClientAssertion(async (AssertionRequestOptions a) =>
+ {
+ Assert.AreEqual(AgentIdentity, a.ClientAssertionFmiPath);
+ var cred = await GetAppCredentialAsync(a.ClientAssertionFmiPath).ConfigureAwait(false);
+ return cred;
+ })
+ .Build();
+
+ var result = await cca1.AcquireTokenForClient([TokenExchangeUrl])
+ .WithFmiPathForClientAssertion(AgentIdentity)
+ .ExecuteAsync().ConfigureAwait(false);
+
+ Trace.WriteLine($"User FIC credential from: {result.AuthenticationResultMetadata.TokenSource}");
+
+ return result.AccessToken;
+ }
+
+ ///
+ /// Builds a signed client assertion JWT using the Wilson library.
+ /// This is the same JWT that WithCertificate would create internally,
+ /// but done manually so we can pass it via WithClientAssertion.
+ ///
+ private static string CreateSignedJwt(string clientId, string audience, X509Certificate2 cert)
+ {
+ var claims = new Dictionary
+ {
+ { "aud", audience },
+ { "iss", clientId },
+ { "jti", Guid.NewGuid().ToString() },
+ { "sub", clientId }
+ };
+
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Claims = claims,
+ SigningCredentials = new X509SigningCredentials(cert)
+ };
+
+ return new JsonWebTokenHandler().CreateToken(tokenDescriptor);
+ }
+
+ #endregion
+ }
+}