Jul 23, 2021 4 min read

Secretless applications: use Azure Identity SDK to access data with a Managed Identity

Use your Managed Identity in .NET code to authenticate against Azure resources, Microsoft Graph, SharePoint and Dataverse/Dynamics 365.

Secretless applications: use Azure Identity SDK to access data with a Managed Identity
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!

Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Yannick Reekmans.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.