I have an existing Entra ID Registered App who's identity I use for accessing Microsoft services from my back-end such as OAuth web-login and Microsoft Store related APIs. These all depend on my existing app's ClientID to filter the results properly.
All of this was working when using the App's secret key to generate the needed access tokens. However, I am no longer able to generate secrets for my Entra App per tenant policy and am trying to implement the best practice of using Azure Managed Identities.
I setup a Managed Identity and configured my existing Entra App to have a Federated Credential (under Certificates and Secrets) that is linked to the Managed Identity that I setup.
I am able to get Tokens using the Managed Identity for my service running on a test VM in Azure. However, the tokens have the "appid" value of the managed identity.
How do you take the tokens generated from the managed identity ID and get an access token so that the "appid" value in the token is the linked Entra Registered app's ClientID? I've seen reference of exchanging the managed identity token for a different app's access token, but I am having a hard time finding an actual example or documentation of how to do that to meet my requirement.
Here is the code that I'm using:
var managedClientId = "7b2a2e38-57c3-4eb8-8ec7-ad7bdbd05ac5";
var audience = "https://onestore.microsoft.com/b2b/keys/create/collections";
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = managedClientId });
var accessToken = credential.GetToken(new TokenRequestContext(new[] { audience }));
The token returned has the following:
{
"aud": "https://onestore.microsoft.com/b2b/keys/create/collections",
"iat": 1752781126,
"nbf": 1752781126,
"exp": 1752867826,
"appid": "7b2a2e38-57c3-4eb8-8ec7-ad7bdbd05ac5",
...
}
Ok, I was able to figure this out by using the following code instead:
Here is the example code that I now have:
/// <summary>
/// Creates an access token for an Entra ID app, but uses Managed Identity assigned to the Azure Resource / VM
/// instead of a Secret key
/// </summary>
/// <param name="audience">Target audience.</param>
/// <returns>If successful, returns managed identity token.</returns>
protected virtual AccessToken CreateAccessTokenFromManagedIdentity(string audience)
{
string appClientId = "[APP ID USING THE MANAGED ID AS A FEDERATED CREDENTIAL]";
string resourceTenantId = "[TENANT ID]";
string miClientId = "[MANAGED CREDENTIAL CLIENT ID]";
string managedIdAudience = "api://AzureADTokenExchange";
ClientAssertionCredential assertion = new(
resourceTenantId,
appClientId,
async (token) => await GetManagedIdentityToken(miClientId, managedIdAudience));
// The scopes need to end with "/.default" here to work
string[] scopes = { $"{audience}/.default" };
// Request an access token for our Client ID using the managed ID credentials as the auth / secret
var token = assertion.GetToken(new TokenRequestContext(scopes));
return token;
}
/// <summary>
/// Gets a token for the user-assigned Managed Identity.
/// </summary>
/// <param name="miClientId">Client ID for the Managed Identity.</param>
/// <param name="audience">Target audience. For public clouds should be api://AzureADTokenExchange.</param>
/// <returns>If successful, returns managed identity access token.</returns>
protected static async Task<string> GetManagedIdentityToken(string miClientId, string audience)
{
var miCredential = new ManagedIdentityCredential(miClientId);
string[] scopes = { $"{audience}/.default" };
return (await miCredential.GetTokenAsync(new Azure.Core.TokenRequestContext(scopes)).ConfigureAwait(false)).Token;
}