[Feature Request] Add support for ITokenAcquisition and debugging locally with Managed Identity
Reading the documentation in Calling APIs with Managed Identity, you get the impression that it would be possible to use the id web library instead of Azure.Identity for getting tokens with Managed Identity.
However, it doesn't seem to work when debugging locally in Visual Studio, as DefaultAzureCredentials do. Is it supposed to work?
Also, the solution and documentation is only focused on console applications, using IDownstreamApi. A more relevant example would be for a Web Api that calls a Web Api with ITokenAcquisition, since there are more suitable strongly typed proxy clients, like Refit. But that doesn't seem to work either?
In my mind, the configuration in "AzureAd" would have a setting for ManagedIdentity, and not in the "downstream api" section. Currently, it only supports federated clients with user assigned MI's.
Getting a token in code works, when deployed in Azure, with the parameter tokenAcquisitionOptions, but since it doesn't work when debugging it won't be pretty, and you would like it to be configurable in appsettings (if you'd like to do ClientCredentials locally for instance).
string token = await _tokenAcquisition.GetAccessTokenForAppAsync("api://{api-uri}/.default",
tokenAcquisitionOptions: new TokenAcquisitionOptions { ManagedIdentity = new() });
Testing locally is definitely necessary. If an upstream api 'has' a managed identity and the downstream api has an app registration with an app role (i.e. 'ManagedIdRole') and the Managed Identity (Enterprise app) has permissions to the ManagedIdRole, I'd expect to have an appsettings.json with the App (Client) Id of the downstreamapi to acquire the tokens.
Thanks for the feedback It's not the design that was chosen. The managed identity is not your app, and can only do limited client credentials for the moment, therefore we chose to expose it as an option of the token acquisition or DownstreamApi.
Because Managed identity is not available locally, I would recommend you use FIC+MSI instead and have 2 credential descriptions in your ClientCredentials collection:
- the first is the FIC+MSI one
- and the second a client cert that you can get from KevVault or locally.
"ClientCredentials": [
{
"SourceType": "SignedAssertionFromManagedIdentity",
"ManagedIdentityClientId": "GUID"
},
{
"SourceType": "KeyVault",
"KeyVaultUrl": "https://msidlabs.vault.azure.net/",
"KeyVaultCertificateName": "IDLABS-APP-Confidential-Client-Cert"
}
],
Hi
As I wrote, I could cope with clientcredentials while debugging (secret or certificate) and MSI when deployed, using different appsettings files. So, with appsettings.Development.json containing SourceType ClientSecret or KeyVault and appsettings.json containing MSI only. This should mean that I can use the same code when acquiring the token:
string token = await _tokenAcquisition.GetAccessTokenForAppAsync("api://{guid}/.default");
However, it doesn't work. When using above code, I get the error:
{"statusCode":400,"message":"No User Assigned or Delegated Managed Identity found for specified ClientId/ResourceId/PrincipalId.","correlationId":"19b96155-11ea-451f-9194-4e38ed69ede3"}
But with the tokenAcquisitionOptions it works.
In your sample above @jmprieur you also have an undocumented setting, ManagedIdentityClientId (the referred documentation has another pattern). That is for user-assigned identities, right? So, with system-assigned identities it should be skipped.
Note, I don't want to use user-assigned identities or FIC.
Maybe related to my issue, with the same scenario I can't figure out how to configure AcquireTokenOptions for managed identity with only environment variables.
What is the equivalent with env vars of
configure.AcquireTokenOptions = new AcquireTokenOptions
{
ManagedIdentity = new ManagedIdentityOptions {
}
};
If it's not possible, could the configuration object be designed so that it is compatible with env var configuration provider ?
To fix the issue, for now, I added a postConfigured option
private static IServiceCollection WithSystemAssignedIdentitySupport(this IServiceCollection services, string serviceName, IConfiguration configuration)
{
return services.PostConfigure<DownstreamApiOptions>(serviceName, configure =>
{
var systemAssigned = configuration.GetValue<bool>("AcquireTokenOptions:ManagedIdentity:SystemAssignedIdentity");
if (systemAssigned)
{
configure.AcquireTokenOptions.ManagedIdentity = new ManagedIdentityOptions
{
};
}
});
}
@scrocquesel-ml150, that might be a neat workaround in some scenarios. However, this ticket is focused on scenarios when not using IDownstreamApi / AddDownstreamApi.
@scrocquesel-ml150, that might be a neat workaround in some scenarios. However, this ticket is focused on scenarios when not using IDownstreamApi / AddDownstreamApi.
@SWarnberg I understand that you were unable to bind an AcquireTokenOptions instance due to how ManagedIdentityOptions is designed. That is we can't bind ManagedIdentityOptions with environment variable to request a system assigned identity.
If that's the case, you can still use my workaround directly with AcquireTokenOptions or other option classes like AuthorizationHeaderProviderOptions.
Be aware that ITokenAcquisition is going to be deprecated, as mentioned in this comment:
https://github.com/AzureAD/microsoft-identity-web/blob/ecd28dda8105c301580279745bd318de4d9e6790/src/Microsoft.Identity.Web.TokenAcquisition/BaseAuthorizationHeaderProvider.cs#L26-L29
I don't use DownstreamApi either. The good news is that Microsoft.Identity option classes are declared in a base abstraction package. So they can be used without importing the Microsoft.Identity.Web.Downstream package. What I do is register named options of type DownstreamApiOptions and use them in a DelegatingHandler. The handler make use of IAuthorizationHeaderProvider as an alternative to ITokenAcquisition.
Thanks for update. So, probably your workaround would work with the simplest solution, like (all configuration checks removed):
builder.Services.PostConfigure<TokenAcquisitionOptions>(configure => { configure.ManagedIdentity = new(); });
But it's still a workaround, and I think it should work with the "AzureAd" settings, just like when deployed in Azure environments:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": ".....",
"ClientId": "....",
"ClientCredentials": [
{
"SourceType": "SignedAssertionFromManagedIdentity"
}
]
}
And yes, I know about IAuthorizationHeaderProvider, but I don't like the way that it adds the scheme in the token string. We just want the token, just like ITokenAcquisition.
Thanks for update. So, probably your workaround would work with the simplest solution, like (all configuration checks removed):
builder.Services.PostConfigure<TokenAcquisitionOptions>(configure => { configure.ManagedIdentity = new(); });
I think that if you hard code the postConfigure this way, you won't be able to run localy, as it will always try to authenticate with a managed identity when requesting token for application.
https://github.com/AzureAD/microsoft-identity-web/blob/ecd28dda8105c301580279745bd318de4d9e6790/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs#L363
But it's still a workaround, and I think it should work with the "AzureAd" settings, just like when deployed in Azure environments:
"AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": ".....", "ClientId": "....", "ClientCredentials": [ { "SourceType": "SignedAssertionFromManagedIdentity" } ] }
As far as I know, SignedAssertionFromManagedIdentity worked in Kubernetes deployments (or used to work). Currently, for other workloads like App Services, you can only use TokenAcquisitionOptions.ManagedIdentity for a secret/certificate-less experience (either with a user-assigned identity or a system-assigned identity).
Locally, you cannot use your developer identity (Azure CLI, Visual Studio, Visual Studio Code, etc.). Historically, SignedAssertionFromManagedIdentity used to use DefaultAzureCredentials with default options. Then, it was narrowed to Azure credentials only (explicitly excluding dev credentials) and then replaced by MSAL ManagedIdentityId. So, it now only supports Managed Identity.
I guess the reason is that you are not the application represented by the clientId, so it is not valid to use an assertion based on your identity to authenticate as the confidential client for requesting tokens.
Thus, locally, the only solution I know of is to use a confidential client configuration with either a client secret or certificate in a Key Vault. It then requires an app registration and admin consent for the app permission.
However, the issue is more general and been reported several times like https://github.com/AzureAD/microsoft-identity-web/issues/2794.
And yes, I know about
IAuthorizationHeaderProvider, but I don't like the way that it adds the scheme in the token string. We just want the token, just likeITokenAcquisition.
+1