Service-to-Service comminucation with Refit & Client Credentials Flow for Entra ID

For a recent project I needed my ASP.NET Core backend service to talk to an external license server using the Entra ID Client Credentials Flow for authentication. For these scenarios I like using Refit to generate the required http client logic. And adding auth for Entra ID is pretty straight forward as well if you know which libraries to use.

Here’s what we want to do:

  • create a Refit Interface for the API we need to call
  • create a provider to automatically get the required Access Token on every API call
  • configure Refit with our auth provider in ASP.NET Core Dependency Injection

For the authentication we’ll be using the Microsoft Authentication Library (MSAL) for .NET aka the Microsoft.Identity.Client NuGet package. The library will handle token caching & refreshing on it’s own so we have just a few lines of code.

Let’s look at the files:

using Refit;
namespace Devsym.License;
[Headers("Authorization: Bearer")]
public interface ILicenseAPI
{
[Get("/licenses/{customerId}")]
Task<LicenseResponse> GetLicense(string customerId);
}
view raw ILicenseAPI.cs hosted with ❤ by GitHub
Refit Interface for License API

The LicenseAuthProdivder will create an IConfidentialClientApplication with either ClientSecret or Certificate depending on your needs (see commented out code).

The GetToken() method will be called by Refit whenever an API call is made.

using Microsoft.Extensions.Options;
using Microsoft.Identity.Client;
namespace Devsym.License;
public class LicenseAuthProvider
{
private readonly LicenseOptions _options;
private readonly IConfidentialClientApplication _app;
public LicenseAuthProvider(IOptions<LicenseOptions> options)
{
_options = options.Value;
//X509Certificate2 certificate = new(_options.CertificatePath);
_app = ConfidentialClientApplicationBuilder.Create(_options.ClientId)
//.WithCertificate(certificate)
.WithClientSecret(_options.ClientSecret)
.Build();
}
public async Task<string> GetToken(CancellationToken ct)
{
AuthenticationResult authResult = await _app.AcquireTokenForClient(scopes: [_options.Scope])
.WithTenantId(_options.TenantId)
.ExecuteAsync(ct);
return authResult.AccessToken;
}
Provider for Token Retreival

To access the license server we need to provide the following options with either ClientSecret or CertificatePath configured.

namespace Devsym.License;
public class LicenseOptions
{
public const string License = "License";
public required string Endpoint { get; set; }
public required string ClientId { get; set; }
public required string ClientSecret { get; set; }
public required string TenantId { get; set; }
public required string Scope { get; set; }
public required string CertificatePath { get; set; }
}
Authentication Options

And to bring everything together we configure Refit to use the LicenseAuthProvider before registering it for Dependency Injection.

// get license options, create LicenseAuthProvider and configure Refit
IOptions<LicenseOptions> licenseOptions = Options.Create<LicenseOptions>();
LicenseAuthProvider licenseAuthProvider = new(licenseOptions);
RefitSettings refitSettings = new()
{
AuthorizationHeaderValueGetter = (message, cancellationToken) => licenseAuthProvider.GetToken(cancellationToken)
};
builder.Services.AddRefitClient<ILicenseAPI>(refitSettings)
.ConfigureHttpClient(c => c.BaseAddress = new Uri(licenseOptions.Endpoint));
view raw Program.cs hosted with ❤ by GitHub

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.