MSAL client type
Confidential
Problem statement
Internal partners moving off passwords for test automation need a first-class MSAL API to acquire tokens for a specific user using User Federated Identity Credentials (User FIC). The underlying token endpoint flow is ROPC-like but uses a federated user credential JWT instead of a password, via grant_type=user_fic, username, and user_federated_identity_credential.
Proposed solution
Proposed API (DevEx)
Summary
Add support for User Federated Identity Credential (UserFIC) flow to IConfidentialClientApplication, enabling acquisition of user-scoped tokens using federated identity credential assertions instead of passwords.
OAuth Flow:
grant_type: user_fic
- Parameters:
username, user_federated_identity_credential (assertion), scope
- Returns user-scoped access token with account information in cache
Proposed API
Core Interface
namespace Microsoft.Identity.Client
{
/// <summary>
/// Provides methods to acquire tokens using User Federated Identity Credential (UserFIC) flow.
/// </summary>
public interface IByUserFederatedIdentityCredential
{
/// <summary>
/// Acquires a user-scoped token using federated identity credential assertion.
/// </summary>
/// <param name="scopes">Scopes requested to access a protected API</param>
/// <param name="username">User principal name (e.g., user@contoso.com)</param>
/// <param name="assertionCallback">
/// Callback to provide the assertion token. Invoked when acquiring new tokens (not from cache).
/// </param>
/// <param name="tokenExchangeScope">
/// Scope for assertion token. Defaults to "api://AzureADTokenExchange/.default".
/// Use "api://AzureADTokenExchangeUSGov/.default" for US Gov,
/// "api://AzureADTokenExchangeChina/.default" for China.
/// </param>
AcquireTokenByUserFederatedIdentityCredentialParameterBuilder
AcquireTokenByUserFederatedIdentityCredential(
IEnumerable<string> scopes,
string username,
Func<Task<string>> assertionCallback,
string tokenExchangeScope = "api://AzureADTokenExchange/.default");
}
}
Parameter Builder
namespace Microsoft.Identity.Client
{
public sealed class AcquireTokenByUserFederatedIdentityCredentialParameterBuilder :
AbstractConfidentialClientAcquireTokenParameterBuilder<AcquireTokenByUserFederatedIdentityCredentialParameterBuilder>
{
public AcquireTokenByUserFederatedIdentityCredentialParameterBuilder WithSendX5C(bool sendX5C);
public AcquireTokenByUserFederatedIdentityCredentialParameterBuilder WithClaims(string claims);
public AcquireTokenByUserFederatedIdentityCredentialParameterBuilder WithCorrelationId(Guid correlationId);
public AcquireTokenByUserFederatedIdentityCredentialParameterBuilder WithProofOfPossession(PoPAuthenticationConfiguration popConfig);
public AcquireTokenByUserFederatedIdentityCredentialParameterBuilder WithForceRefresh(bool forceRefresh);
public Task<AuthenticationResult> ExecuteAsync(CancellationToken cancellationToken = default);
}
}
Usage Examples
Example 1: Managed Identity as Assertion Source
// Setup (reuse these instances)
var miApp = ManagedIdentityApplicationBuilder
.Create(ManagedIdentityId.SystemAssigned)
.Build();
var confidentialApp = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithCertificate(cert)
.Build();
// Acquire user-scoped token
var result = await confidentialApp
.AcquireTokenByUserFederatedIdentityCredential(
scopes: new[] { "User.Read", "Mail.Send" },
username: "user@contoso.com",
assertionCallback: async () =>
{
var miResult = await miApp
.AcquireTokenForManagedIdentity("api://AzureADTokenExchange/.default")
.ExecuteAsync();
return miResult.AccessToken;
})
.ExecuteAsync();
// Result contains user context
Console.WriteLine($"User: {result.Account.Username}");
Console.WriteLine($"Token Source: {result.AuthenticationResultMetadata.TokenSource}"); // IdentityProvider
Example 2: Client Credentials (Certificate) as Assertion Source
// Separate app for getting assertions (using certificate)
var assertionApp = ConfidentialClientApplicationBuilder
.Create(assertionAppId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithCertificate(assertionCert)
.Build();
var mainApp = ConfidentialClientApplicationBuilder
.Create(mainAppId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithCertificate(mainCert)
.Build();
// Acquire user token using client credential assertion
var result = await mainApp
.AcquireTokenByUserFederatedIdentityCredential(
scopes: new[] { "User.Read" },
username: "user@contoso.com",
assertionCallback: async () =>
{
var assertionResult = await assertionApp
.AcquireTokenForClient(new[] { "api://AzureADTokenExchange/.default" })
.ExecuteAsync();
return assertionResult.AccessToken;
})
.ExecuteAsync();
Example 3: Recommended Pattern - Use cache for subsequent calls
// First call
var result = await GetTokenAsync(username, scopes, null).ConfigureAwait(false);
IAccount account = await cca.GetAccountAsync(result.Account.HomeAccountId.Identifier).ConfigureAwait(false);
// Subsequent calls to get the token from the cache
result = await GetTokenAsync(username, scopes, account).ConfigureAwait(false);
public async Task<AuthenticationResult> GetTokenAsync(string username, string[] scopes, IAccount account)
{
// Try silent first if account is not null
if (account != null)
{
try
{
return await _app.AcquireTokenSilent(scopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// Expected - fall through to UserFIC
}
}
// Acquire new token with UserFIC
return await _app
.AcquireTokenByUserFederatedIdentityCredential(
scopes: scopes,
username: username,
assertionCallback: async () =>
{
var miResult = await _miApp
.AcquireTokenForManagedIdentity("api://AzureADTokenExchange/.default")
.ExecuteAsync();
return miResult.AccessToken;
})
.ExecuteAsync();
}
Key Design Decisions
1. Callback-Based Approach
- Flexibility: Supports any assertion source (Managed Identity, Client Credentials, custom)
- Testability: Easy to mock callbacks
- Lazy Evaluation: Callback only invoked when acquiring new tokens (not from cache)
- Consistency: Similar pattern to
AcquireTokenOnBehalfOf
2. Token Exchange Scope
- Default:
api://AzureADTokenExchange/.default (public cloud)
- Configurable: Developer must explicitly provide scope for sovereign clouds
- Rationale: Most common scenario is public cloud; explicit opt-in for others
3. Silent Flow
- Developer Responsibility: Developer chooses when to try silent first
- Pattern: Try
AcquireTokenSilent, catch MsalUiRequiredException, fall back to UserFIC
- Rationale: Gives developers full control over flow
4. Cache Behavior
- User Token Cache: Tokens stored with user context (same as ROPC, OBO)
- Cache Key:
{clientId}_{tenantId}_{username}_{scopes}
- Account Object: Populated with user information
- Silent Calls: Standard
AcquireTokenSilent works with returned account
Callback Behavior
| Scenario |
Callback Invoked? |
Token Source |
First AcquireTokenByUserFederatedIdentityCredential call |
✅ Yes |
IdentityProvider |
AcquireTokenSilent (cache hit) |
❌ No |
Cache |
AcquireTokenSilent (cache miss) |
❌ No (throws) |
N/A |
WithForceRefresh(true) |
✅ Yes |
IdentityProvider |
Implementation Impact
New Files
src/client/Microsoft.Identity.Client/IByUserFederatedIdentityCredential.cs
src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUserFederatedIdentityCredentialParameterBuilder.cs
src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByUserFederatedIdentityCredentialParameters.cs
src/client/Microsoft.Identity.Client/Internal/Requests/UserFederatedIdentityCredentialRequest.cs
Modified Files
src/client/Microsoft.Identity.Client/IConfidentialClientApplication.cs (add interface)
src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs (implement interface)
src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs (add executor method)
src/client/Microsoft.Identity.Client/OAuth2/OAuth2GrantType.cs (add UserFic constant)
src/client/Microsoft.Identity.Client/OAuth2/OAuth2Parameter.cs (add UserFederatedIdentityCredential constant)
- PublicAPI files (all targets)
Testing
- Integration tests with lab infrastructure (Managed Identity + Client Credentials scenarios)
- Unit tests for with mocks
- Tests to validate the cache is used to acquire the token
Alternatives
Option 2: Integrated Managed Identity or Confidential Client Application
Configure at builder level:
.WithManagedIdentityForUserFic(ManagedIdentityId.SystemAssigned, tokenExchangeUrl)
or
.WithConfidentialClientForUserFic(confidentialApp, tokenExchangeUrl)
Pro's:
- Simpler API for common scenarios
- Encapsulates assertion acquisition logic
- Less boilerplate for developers
- More explicit what the API can do
Con's:
- Less flexible for custom assertion sources
- Will require additional configuration to handle caching or non cache scenarios
Option 3: Provider Interface
Option 3 proposes a flexible provider-based approach for supporting various assertion sources in the UserFIC flow. Developers register an assertion provider up front, then acquire user-scoped tokens using a new, first-class API that invokes the provider as needed.
Silent authentication behavior will follow the pattern established for ROPC: The UserFIC acquisition call does not implicitly attempt cache reads. If a cache-first pattern is needed, developers should call AcquireTokenSilent themselves, then fall back to UserFIC as appropriate.
Provider Interface
A new interface supplies the assertion required for the UserFIC protocol:
public interface IUserFicAssertionProvider
{
Task<string> GetAssertionAsync(
string username,
string tokenExchangeScope,
CancellationToken cancellationToken);
}
Configuration
Developers configure the provider and the default token exchange scope once when building the Confidential Client:
var cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithCertificate(cert)
.WithUserFicAssertionProvider(
provider: myProvider,
tokenExchangeScope: "api://AzureADTokenExchange/.default")
.Build();
Token Acquisition (Dev Experience)
Developers acquire tokens by username using the configured provider as follows:
var result = await cca
.AcquireTokenByUserFederatedIdentityCredential(
scopes: new[] { "User.Read" },
username: "user@contoso.com")
.ExecuteAsync(ct);
- The configured provider supplies the assertion automatically.
- No implicit cache lookup is performed by this call.
WithForceRefresh(true) can be supplied on the builder if needed, matching options for other MSAL APIs.
Silent Authentication Pattern (ROPC-style)
The recommended ROPC-style pattern for devs to ensure cache-first behavior:
try
{
var account = await cca.GetAccountAsync(homeAccountId).ConfigureAwait(false);
return await cca.AcquireTokenSilent(scopes, account).ExecuteAsync(ct);
}
catch (MsalUiRequiredException)
{
// Silent failed, fallback to UserFIC
return await cca
.AcquireTokenByUserFederatedIdentityCredential(scopes, username)
.ExecuteAsync(ct);
}
Additional Context:
- Similar to ROPC but uses assertion instead of password
- Tokens are user-scoped (stored in user cache partition)
- Works with standard
AcquireTokenSilent for cache retrieval
- Callback pattern ensures fresh assertions on force refresh
- Supports all sovereign clouds with explicit scope configuration
No response
MSAL client type
Confidential
Problem statement
Internal partners moving off passwords for test automation need a first-class MSAL API to acquire tokens for a specific user using User Federated Identity Credentials (User FIC). The underlying token endpoint flow is ROPC-like but uses a federated user credential JWT instead of a password, via grant_type=user_fic, username, and user_federated_identity_credential.
Proposed solution
Proposed API (DevEx)
Summary
Add support for User Federated Identity Credential (UserFIC) flow to
IConfidentialClientApplication, enabling acquisition of user-scoped tokens using federated identity credential assertions instead of passwords.OAuth Flow:
grant_type:user_ficusername,user_federated_identity_credential(assertion),scopeProposed API
Core Interface
Parameter Builder
Usage Examples
Example 1: Managed Identity as Assertion Source
Example 2: Client Credentials (Certificate) as Assertion Source
Example 3: Recommended Pattern - Use cache for subsequent calls
// First call
var result = await GetTokenAsync(username, scopes, null).ConfigureAwait(false);
IAccount account = await cca.GetAccountAsync(result.Account.HomeAccountId.Identifier).ConfigureAwait(false);
// Subsequent calls to get the token from the cache
result = await GetTokenAsync(username, scopes, account).ConfigureAwait(false);
Key Design Decisions
1. Callback-Based Approach
AcquireTokenOnBehalfOf2. Token Exchange Scope
api://AzureADTokenExchange/.default(public cloud)3. Silent Flow
AcquireTokenSilent, catchMsalUiRequiredException, fall back to UserFIC4. Cache Behavior
{clientId}_{tenantId}_{username}_{scopes}AcquireTokenSilentworks with returned accountCallback Behavior
AcquireTokenByUserFederatedIdentityCredentialcallAcquireTokenSilent(cache hit)AcquireTokenSilent(cache miss)WithForceRefresh(true)Implementation Impact
New Files
src/client/Microsoft.Identity.Client/IByUserFederatedIdentityCredential.cssrc/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenByUserFederatedIdentityCredentialParameterBuilder.cssrc/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenByUserFederatedIdentityCredentialParameters.cssrc/client/Microsoft.Identity.Client/Internal/Requests/UserFederatedIdentityCredentialRequest.csModified Files
src/client/Microsoft.Identity.Client/IConfidentialClientApplication.cs(add interface)src/client/Microsoft.Identity.Client/ConfidentialClientApplication.cs(implement interface)src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs(add executor method)src/client/Microsoft.Identity.Client/OAuth2/OAuth2GrantType.cs(addUserFicconstant)src/client/Microsoft.Identity.Client/OAuth2/OAuth2Parameter.cs(addUserFederatedIdentityCredentialconstant)Testing
Alternatives
Option 2: Integrated Managed Identity or Confidential Client Application
Configure at builder level:
.WithManagedIdentityForUserFic(ManagedIdentityId.SystemAssigned, tokenExchangeUrl)or
.WithConfidentialClientForUserFic(confidentialApp, tokenExchangeUrl)Pro's:
Con's:
Option 3: Provider Interface
Option 3 proposes a flexible provider-based approach for supporting various assertion sources in the UserFIC flow. Developers register an assertion provider up front, then acquire user-scoped tokens using a new, first-class API that invokes the provider as needed.
Silent authentication behavior will follow the pattern established for ROPC: The UserFIC acquisition call does not implicitly attempt cache reads. If a cache-first pattern is needed, developers should call
AcquireTokenSilentthemselves, then fall back to UserFIC as appropriate.Provider Interface
A new interface supplies the assertion required for the UserFIC protocol:
Configuration
Developers configure the provider and the default token exchange scope once when building the Confidential Client:
Token Acquisition (Dev Experience)
Developers acquire tokens by username using the configured provider as follows:
WithForceRefresh(true)can be supplied on the builder if needed, matching options for other MSAL APIs.Silent Authentication Pattern (ROPC-style)
The recommended ROPC-style pattern for devs to ensure cache-first behavior:
Additional Context:
AcquireTokenSilentfor cache retrievalNo response