Skip to content

Microsoft Identity

Cratis’ Arc provides a way to easily work with providing an object that represents properties the application finds important for describing the logged in user. The purpose of this is to provide details about the logged in user on the ingress level of an application and letting it provide the details on the request going in. Having it on the ingress level lets you expose the details to all microservices behind the ingress.

The values provided by the provider are values that are typically application specific and goes beyond what is already found in the token representing the user. This is optimized for working with Microsoft Azure well known HTTP headers passed on by the different app services, such as Azure ContainerApps or WebApps. Internally, it is based on the following HTTP headers to be present.

HeaderDescription
x-ms-client-principalThe token holding all the details, base64 encoded Microsoft Client Principal Data definition
x-ms-client-principal-idThe unique identifier from the identity provider for the identity
x-ms-client-principal-nameThe name of the identity, typically resolved from claims within the token

Important note: Since local development is not configured with the identity provider, but you still need a way to test that both the backend and the frontend deals with the identity in the correct way. This can be achieved by creating the correct token and injecting it as request headers using a browser extension. Read more about generating principal tokens for local development.

The token in the x-ms-client-principal should be a base64 encoded Microsoft Client Principal Data definition.

To get the Microsoft Client Principal supported in your backend, the Arc offers an AuthenticationHandler that supports the HTTP headers and does the right thing to put ASP.NET Core and every HttpContext in the right state.

You can add this by calling the AddMicrosoftIdentityPlatformIdentityAuthentication() method on your services.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMicrosoftIdentityPlatformIdentityAuthentication();

The above code will then also call the .AddAuthentication() with the default scheme name (MicrosoftIdentityPlatform) and register the appropriate AuthenticationHandler for that scheme.

You can override the scheme name on the extension method by passing your own string as an argument.

For it to be appropriately setup, you’ll need to enable the default authentication and authorization on your app, like below:

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

Knowing which identity provider signed the caller in

Section titled “Knowing which identity provider signed the caller in”

The x-ms-client-principal payload carries an identityProvider field describing the provider the ingress authenticated the caller with. Without it, everything downstream sees is a set of claims that look the same whether they came from Entra ID or GitHub — so the application has no way to tell one federation apart from another.

Arc keeps that value on the reconstructed principal as a claim, so both normal request authorization and the /.cratis/me identity resolution can read it:

using Cratis.Arc.Identity;
using Microsoft.AspNetCore.Http;
public class IdentityProviderReader(IHttpContextAccessor httpContextAccessor)
{
public string? Current =>
httpContextAccessor.HttpContext?.User.FindFirst(MicrosoftIdentityPlatformClaims.IdentityProvider)?.Value;
}
AspectDetail
Claim typeurn:cratis:arc:identity:provider — use the MicrosoftIdentityPlatformClaims.IdentityProvider constant
ValueThe exact identityProvider value the ingress forwarded, for example aad or github
When absentThe forwarded principal carried no identityProvider field, or the field held only blank characters

The claim type is reserved for Arc. The x-ms-client-principal header is base64, not a signature, so any caller that can reach the application can put whatever it likes in the serialized payload — including a claim of this very type. Arc therefore removes every claim of the reserved type from the deserialized payload, ignoring casing, before writing its own value.

Read the claim with FindFirst or FindAll, as in the example above, and never normalize the claim type yourself. Those lookups compare the claim type the same way the strip does, so what they return is exactly what Arc wrote. Enumerating User.Claims and folding the type with ToUpperInvariant() or Trim() widens the match beyond what was removed — a forged type differing only by Unicode case folding (urn:cratiſ:arc:identity:provider, U+017F) or by trailing whitespace then matches, and because forwarded claims are added before Arc’s own, a FirstOrDefault() over that widened set returns the forgery.

For information about providing additional identity details for logged-in users, including authorization checks and custom identity information, see the Identity documentation.

The Microsoft Identity integration works seamlessly with the generic identity system to provide domain-specific information beyond what’s available in identity provider tokens.