-
Notifications
You must be signed in to change notification settings - Fork 395
Open
Description
Summary
Propose the addition of helper factory methods to FederatedCredentialProvider that simplify acquiring a FIC assertion using either a Managed Identity or a Confidential Client Application (CCA), for use with IByUserFederatedIdentityCredential.AcquireTokenByUserFederatedIdentityCredential.
Motivation and goals
- Developers using the
user_ficgrant type must currently build their own assertion provider delegate, which requires boilerplate setup of a Managed Identity or CCA app and manual token acquisition. - Helper methods would remove this friction and make the correct usage pattern the default.
- PR Implement UserFIC: IByUserFederatedIdentityCredential interface and user_fic grant type #5802 introduced the
IByUserFederatedIdentityCredentialinterface but deferred the helper methods pending design discussion (see discussion comment). - The exact API shape needs to be agreed upon with the team, specifically to account for bound FIC and whether to express bound/bearer as a parameter or as separate methods.
In scope
FederatedCredentialProvider.FromManagedIdentity(...)— builds an assertion provider using Managed Identity, accepting:ManagedIdentityId managedIdentityIdstring audience(defaulted to"api://AzureADTokenExchange/.default")
FederatedCredentialProvider.FromConfidentialClient(...)— builds an assertion provider using a caller-provided Confidential Client Application, accepting:IConfidentialClientApplication ccastring audience(defaulted to"api://AzureADTokenExchange/.default")
- Both methods return
Func<AssertionRequestOptions, Task<string>>(async). A sync overload is a potential discussion point with the team. - Design discussion and decision on API shape for bound vs. bearer FIC. Bound FIC can pass additional claims via
AssertionRequestOptions. Two options being considered:- Option 1 — single method with
isBoundparameter:Func<AssertionRequestOptions, Task<string>> FromManagedIdentity(ManagedIdentityId id, string audience = "api://AzureADTokenExchange/.default", bool isBound = false)
- Option 2 — separate methods per scenario:
Func<AssertionRequestOptions, Task<string>> FromManagedIdentityBearer(ManagedIdentityId id, string audience = "api://AzureADTokenExchange/.default") Func<AssertionRequestOptions, Task<string>> FromManagedIdentityBound(ManagedIdentityId id, string audience = "api://AzureADTokenExchange/.default")
- Option 1 — single method with
- The team (@bgavrilMS, @gladjohn) to align on which approach best covers current and future scenarios before implementation.
Out of scope
- Protocol-level changes to the bound FIC flow itself.
- Changes to
IByUserFederatedIdentityCredentialorAcquireTokenByUserFederatedIdentityCredentialsignatures. - Custom or third-party assertion provider implementations (the helpers are opt-in convenience wrappers).
- Final async vs. sync design decision is a follow-up discussion item, not a blocker.
Risks / unknowns
Token caching behavior:
FromManagedIdentity: The Managed Identity application uses a static token cache, so repeated calls to the returned delegate will always benefit from cache reuse automatically. No additional guidance or configuration is needed from the caller — cached tokens will be returned if still valid, avoiding unnecessary network calls.FromConfidentialClient: The caller provides anIConfidentialClientApplicationinstance. Since the CCA instance and its token cache are owned by the caller, the guidance is:- Preferred: Reuse the same CCA instance across calls so the token cache is shared and repeated calls return cached tokens.
- Alternative: Enable static token caching on the CCA so that multiple CCA instances share a single cache.
⚠️ If neither approach is followed and a new CCA is created per call, a fresh token will be acquired on every assertion request, resulting in unnecessary network calls and risk of throttling.
Other risks:
- Risk: If both async and sync overloads are provided, the sync variant must be carefully evaluated to avoid deadlocks in environments that do not support synchronous blocking (e.g., ASP.NET classic).
- Helpers must propagate
AssertionRequestOptions.CancellationTokento innerExecuteAsynccalls for proper cancellation support.
Examples
Using FromManagedIdentity (system-assigned):
// Static cache is used automatically — no extra configuration needed
var assertionProvider = FederatedCredentialProvider.FromManagedIdentity(
ManagedIdentityId.SystemAssigned);
var result = await (app as IByUserFederatedIdentityCredential)
.AcquireTokenByUserFederatedIdentityCredential(scopes, "user@contoso.com", assertionProvider)
.ExecuteAsync();Using FromManagedIdentity (user-assigned, with explicit audience):
var assertionProvider = FederatedCredentialProvider.FromManagedIdentity(
ManagedIdentityId.WithUserAssignedClientId("mi-client-id"),
audience: "api://AzureADTokenExchange/.default");
var result = await (app as IByUserFederatedIdentityCredential)
.AcquireTokenByUserFederatedIdentityCredential(scopes, "user@contoso.com", assertionProvider)
.ExecuteAsync();Using FromConfidentialClient — reuse the same CCA instance (recommended):
// Build once and reuse — token cache is shared across all calls
var ccaForAssertion = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithCertificate(cert)
.WithAuthority(authority)
.Build();
var assertionProvider = FederatedCredentialProvider.FromConfidentialClient(
ccaForAssertion,
audience: "api://AzureADTokenExchange/.default");
var result = await (app as IByUserFederatedIdentityCredential)
.AcquireTokenByUserFederatedIdentityCredential(scopes, "user@contoso.com", assertionProvider)
.ExecuteAsync();// Bad: a new CCA is created on every call, token cache is never reused
var result = await (app as IByUserFederatedIdentityCredential)
.AcquireTokenByUserFederatedIdentityCredential(
scopes, "user@contoso.com",
FederatedCredentialProvider.FromConfidentialClient(
ConfidentialClientApplicationBuilder.Create(clientId)
.WithCertificate(cert)
.WithAuthority(authority)
.Build()))
.ExecuteAsync();Reactions are currently unavailable