Coding Stephan

Secretless confidential applications

The Microsoft Authentication Library (MSAL) is one of my favorite libraries. It eases the process of getting tokens for Entra ID protected resources, and since most developers seem to struggle with getting tokens that is great. What is not so great, is the (out-of-the-box) lack of support for managed identities in confidential client applications. And I’m all-in for trying to get rid of secrets in applications, so I wanted to help all other developers.

ConfidentialClientApplicationBuilder

In the Microsoft.Identity.Client package you can find the ConfidentialClientApplicationBuilder class. It has support for client secrets (with WithClientSecret) and client certificates (with WithCertificate), both ways are no longer the recommended way to authenticate to Entra ID.

In the end it is doing a get token request, but we want the third option, which is use a managed identity as federated credential. Out of the box, MSAL does not support this. So let me fix that.

  1. Add your managed identity as a federated credential to your app registration in Entra ID.
  2. Add the Azure.Identity package to your project.
  3. Add the Svrooij.Identity.Client.Extensions package to your project.
  4. Use the WithTokenCredential extension method to add a TokenCredential to your confidential client application.
dotnet add package Microsoft.Identity.Client
dotnet add package Azure.Identity
dotnet add package Svrooij.Identity.Client.Extensions
using Azure.Identity;
using Microsoft.Identity.Client;
using Svrooij.Identity.Client.Extensions;

var tokenCredential = new ManagedIdentityCredential();

var app = ConfidentialClientApplicationBuilder
    .Create("your-client-id")
    .WithTenantId("your-tenant-id")
    // šŸ‘€ šŸ‘‡ No secrets here
    .WithTokenCredential(tokenCredential) 
    .Build();

// Use client credentials flow to get a token
var result = await app
    .AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
    .ExecuteAsync();

On-behalf of flow

The same application as above can also be used in the on-behalf of flow. You just need to acquire a token using the AcquireTokenOnBehalfOf method instead of the AcquireTokenForClient method.

var userAssertion = new UserAssertion("user-access-token");
var result = await app.AcquireTokenOnBehalfOf(
    new[] { "https://graph.microsoft.com/user.read" },
    userAssertion)
  .ExecuteAsync();

Why a library?

Why I had to create a library just for this? Because I want to motivate people to stop using secrets in their applications. And adding this extensions method makes it easier for fellow developers to do just that. The code that does this is only a few lines of code. And I hope it will help you to get rid of secrets in your applications.

If you don’t want to use the library copy this code to your project and use that instead:

using Microsoft.Identity.Client;

namespace Microsoft.Identity.Client;

/// <summary>
/// Token credential extensions for Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
/// </summary>
internal static class ConfidentialClientApplicationBuilderExtensions
{
    private const string DEFAULT_TOKEN_CREDENTIAL_AUDIENCE = "api://AzureADTokenExchange";

    /// <summary>
    /// Configure the ConfidentialClientApplicationBuilder to use a managed identity or other Azure.Core.TokenCredential instead of a secret or certificate.
    ///     This will use the managed identity as a federated credential to obtain a token from Entra ID.
    /// </summary>
    /// <param name="builder"></param>
    /// <param name="tokenCredential">What <see cref="Azure.Core.TokenCredential"/> should be used, does not work well with all credentials in `DefaultCredential`, in combination with the default audience.</param>
    /// <param name="audience">What audience should be used, default is `api://AzureADTokenExchange`</param>
    /// <returns></returns>
    public static ConfidentialClientApplicationBuilder WithTokenCredential(
        this ConfidentialClientApplicationBuilder builder,
        Azure.Core.TokenCredential tokenCredential,
        string audience = DEFAULT_TOKEN_CREDENTIAL_AUDIENCE
        )
    {

        return builder.WithClientAssertion(async (options) =>
        {
            var accessTokenResult = await tokenCredential
                .GetTokenAsync(new Azure.Core.TokenRequestContext(new[] { audience }), options.CancellationToken);
            return accessTokenResult.Token;
        });
    }
}

Series: Federated Credentials

Conclusion

Please stop using secrets in your applications! With the (re-)release of multi-tenant managed identity support in December 2024 there is no excuse anymore. Use managed identities as federated credentials to authenticate your confidential client applications in Entra ID. If you need help, let me know on LinkedIn.