How to enrich Application Insights with user information from Azure AD

post-thumb

If you secure your web api with Azure AD, you can enrich your application insights data with user information from the JWT token.

Application Insights is a great service in combination with ASP.NET Core web apis. Just with a a few steps, you get tracking of the incoming HTTP requests, tracking of all uncaught exceptions, tracking all your logs written with the ILogger interface as traces and last but not least, tracking outgoing network traffic as dependencies of for your web api.

After running your web api for a couple of days, you will see all the information in the „transaction search“ tab as seen on the sample screenshot below.

But what if your web api handels requests in context of authenticated Azure AD users? Application Insights contains an whole tab group to investigate the „usage“ of your application. To be honest, it’s intended for applications that have an user interface and in case of a web api, you wont’t get a lot of insights from here.

By default, Application Insights will track all data to a single „anonymous“ user in a single „anonymous“ session. Thats because of missing context information and here this blog post comes into place, to show you how to enrich AppInsights data, so that you can track signed-in Azure AD users and sessions.

Enrich the Telemetry Context

To enrich the telemetry of application Insights, you need to write your own TelemetryInitializer. The idea behind the following implementation is, to use the preferred_username as the User Id that is displayed in the application insights UI, so that an operator can filter all requests made by a user only knowing his Azure AD username. Typically this is the E-Mail address of the user. Additionally this implementation sets the oid object id, the tid tenant id, the sid session id and the azp client id.

public class EnrichContextWithAzureAdClaims : TelemetryInitializerBase
{
    public EnrichContextWithAzureAdClaims(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
    {
    }

    protected override void OnInitializeTelemetry(HttpContext platformContext, RequestTelemetry requestTelemetry, ITelemetry telemetry)
    {
        // read desired information from the claims send with the JWT token.
        var userName = platformContext?.User.FindFirst("preferred_username")?.Value;
        var objectId = platformContext?.User.FindFirst("oid")?.Value;
        var tenantId = platformContext?.User.FindFirst("tid")?.Value;
        var sessionId = platformContext?.User.FindFirst("sid")?.Value;
        var applicationId = platformContext?.User.FindFirst("azp")?.Value;

        telemetry.Context.User.Id = userName;
        telemetry.Context.User.AuthenticatedUserId = objectId;
        telemetry.Context.User.AccountId = tenantId;
        telemetry.Context.Session.Id = sessionId;

        telemetry.Context.GlobalProperties["ApplicationId"] = applicationId;
    }
}

Be aware that you have to explicitly configure your application registration so that every access token for your api contains the preferred_username and sid claim.

To use the Telemetry Initializer, you have to modify the Application Startup class as seen below. Be aware that the asp.net framework changes the JWT claim names to be backward compatible with the SAML protocol. In newer implementations this is normally not required and you can opt out to this.

    public class Startup
    {               
        public void ConfigureServices(IServiceCollection services)
        {
            [...]

            // register application insights and the EnrichContextWithAzureAdClaims TelemetryInitializer
            services.AddApplicationInsightsTelemetry();
            services.AddSingleton<ITelemetryInitializer, EnrichContextWithAzureAdClaims>();

            // register the HttpContextAccessor because EnrichContextWithAzureAdClaims needs to access the HTTP Context to read information out of the claims.
            services.AddHttpContextAccessor();

            [...]

            // don't let the asp.net framework change the claim names to old school SAML names.
            // see https://mderriey.com/2019/06/23/where-are-my-jwt-claims/ for background details.
            services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme,
                options =>
                {
                    options.MapInboundClaims = false;
                    options.TokenValidationParameters.NameClaimType = "name";
                    options.TokenValidationParameters.RoleClaimType = "roles";
                });

            [...]
        }
    }

And thats it! With this configuration you should see that different users are using your api as on the screenshot below.

Der Objektkultur-Newsletter

Mit unserem Newsletter informieren wir Sie stets über die neuesten Blogbeiträge,
Webcasts und weiteren spannende Themen rund um die Digitalisierung.

Newsletter abonnieren