While listening to the Secretless Apps with Christos Matskas episode on the M365 Developer Podcast it reminded me that still a lot of developers have no clue about Managed Identities and their advantages. Additionally, there were some unanswered questions in that episode that I could answer in a couple of blog posts. Thus a new series is born!
After learning about Managed Identities and adding permissions to them, it's time to access data in other Azure resources and protected by Azure AD using those Managed Identities.
Identity SDK for Credentials
Before you can authenticate to a resource, you need a credential to authenticate with and this will come from the Azure Identity SDK. Versions of the SDK exist for many programming languages, but for the purpose of this blog I'll be using only the .NET SDK. It comes as a Nuget package, and the documentation is worth a read.
After installing the Nuget package, there are many ways to get your credentials. If you know upfront which credentials to use, you instantiate that specific class. There are multiple to choose from depending on your scenario, I'll show a subset so you get the gist of it:
- ManagedIdentityCredential: authenticates the Managed Identity of an Azure resource
- EnvironmentCredential: authenticates a service principal or user via credential information specified in environment variables
- ClientSecretCredential: authenticates a service principal using a secret
- VisualStudio(Code)Credential: authenticate in a development environment with Visual Studio or Visual Studio Code
- AzureCliCredential: authenticate in a development environment with the Azure CLI
Example usage:
using Azure.Identity;
var tokenCredential = new AzureCliCredential();
When you have multiple methods you'd like to try in sequence, you can use either DefaultAzureCredential
or ChainedTokenCredential
:
- DefaultAzureCredential: uses a predefined sequence of credential classes (Environment, Managed Identity, Visual Studio, Azure CLI, Azure PowerShell & Interactive). It can be configured by using DefaultAzureCredentialOptions.
- ChainedTokenCredential: it's a wrapper class that accepts (an unlimited number of) credential classes, which will be used in the specified order to try and authenticate.
In this example, the code would first try and authenticate with a Managed Identity, and then switch over to Visual Studio and Azure CLI credentials. It allows specifying the User Assigned Managed Identity client id in the app settings so the code would automatically try that, instead of a System Assigned Managed Identity. This is particularly useful for local debugging of Azure Functions, for example:
using Azure.Identity;
var tokenCredental = new ChainedTokenCredential(
new ManagedIdentityCredential(string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("UserAssignedIdentity")) ? null : Environment.GetEnvironmentVariable("UserAssignedIdentity")),
new VisualStudioCredential(),
new AzureCliCredential()
);
SDK's for accessing specific resources
Before you can use a Managed Identity, you need it to have the correct permissions. I covered that in Secretless applications: add permissions to a Managed Identity
The sample code, as a couple of Azure Functions, is stored in this GitHub repository.
Azure SDK's
The Azure SDKs are very consistent regarding the identity story: all of them support the usage of a TokenCredential
object in the constructor to use as an authentication method. Pretty convenient!
You'll need the right Nuget package for the Azure service you'd like to use. Here is an example to fetch a secret from a Key Vault using Azure.Security.KeyVault.Secrets:
using Azure.Securtity.KeyVault.Secrets;
var client = new SecretClient(
new Uri($"https://{vaultName}.vault.azure.net/"),
token
);
var secret = await client.GetSecretAsync(secretName);
Microsoft Graph
The Microsoft Graph team took a page out of Azure's book and decided that it doesn't make sense to do their own identity approach, so with the just-released version 4 of the Graph SDK they now also natively accept a TokenCredential
object:
using Microsoft.Graph;
var graphClient = new GraphServiceClient(tokenCredential);
SharePoint
The SharePoint CSOM (or PnP.Framework for that matter) package doesn't natively support a TokenCredential
so we need a bit more code to make that work:
using Microsoft.SharePoint.Client;
using (var clientContext = new ClientContext(siteUrl))
{
clientContext.ExecutingWebRequest += (sender, args) =>
{
var resourceUri = args.WebRequestExecutor.WebRequest.RequestUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.UriEscaped);
var token = tokenCredential.GetToken(new TokenRequestContext(new[] { resourceUri }), default).Token;
args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + token;
};
}
This code uses the GetToken
method to fetch an access token for the specified resource. On every request, the ExecutingWebRequest
event handler will fire, find the SharePoint Site URL from the arguments and transform it into the correct resource URI to fetch an access token for SharePoint.
When it has the token, it adds it as a Bearer token to the Authorization
header.
Dynamics 365 / Dataverse
Dataverse has a different way of assigning permissions to a Service Principal or Managed Identity. It uses a concept called Application User. In essence, it is a user object within Dataverse that is directly linked to the Service Principal/Managed Identity. That Application User can then be assigned the appropriate security roles within the Dataverse environment.
Like with SharePoint, the SDK for Dataverse also doesn't natively support the TokenCredential
object which means we will have to use a similar approach to fetch the correct token:
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.Crm.Sdk.Messages;
var serviceClient = new ServiceClient(ConfigHandler.DataverseUrl, async (string dataverseUri) => {
dataverseUri = new Uri(dataverseUri).GetComponents(UriComponents.SchemeAndServer, UriFormat.UriEscaped);
return (await tokenCredential.GetTokenAsync(new TokenRequestContext(new[] { dataverseUri }), default)).Token;
}, true);
if (!serviceClient.IsReady) throw new Exception("Authentication Failed!");
var whoAmIResponse = (WhoAmIResponse)serviceClient.Execute(new WhoAmIRequest());
Conclusion
The above proofs that Managed Identities can be used pretty easily in your code, while still supporting development environments where Managed Identities don't exist as well as show that they aren't limited to Azure resources only!