Edit

Share via


Use Serilog to troubleshoot Microsoft Entra protected Web API authentication or authorization errors

When a web API application calls a web API that's protected with Microsoft Entra ID, authentication or authorization errors might occur due to JwtBearer event validation failures. To troubleshoot this issue, this article introduces a sample web API application named Net6WebAPILogging to set and collect logs for JwtBearer events.

Net6WebAPILogging sample application

This sample web API application assumes you already have a web API registered in Microsoft Entra ID. It uses Microsoft .NET 6 Framework and Microsoft Identity Web NuGet package.

It uses the following methods to set and collect logs for JwtBearer events:

  • Use the JwtBearerEvents class to configure middleware events. JWT Bearer token might fail to validate OnTokenValidated, OnMessageReceived, OnAuthenticationFailed, and OnChalleenge events.
  • Set up logging for JwtBearer events.
  • Use the Serilog framework to log the Debug output to the console window and the local file whose path is specified in the appsettings.json file.

Run the sample application

To run the sample application, you must perform the following steps:

Step 1: Configure the application ID URI for a protected web API

To add the application ID URI for a web API, follow these steps:

  1. In the Azure portal, navigate to the app registration of the web API.

  2. Select Expose an API under Manage.

  3. At the top of the page, select Add next to Application ID URI. The default is api://<application-client-id>.

    Screenshot that shows how to set the application ID URI in an app registration.

  4. Select Save.

Step 2: Change the sample application configuration

Change the following information in the AzureAd section in the appsettings.json file with your own app registration information:

"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<tenant name>.onmicrosoft.com", // for example contoso.onmicrosoft.com
    "TenantId": "<tenant ID>",
    "ClientId": "<application-client-id>"
  },

Step 3: Change the sample application code

Change the ValidAudiences and ValidIssuers properties of the TokenValidationParameters class in the Program.cs file.

Step 4: Configure Serilog

Configure Serilog in the Serilog section in the appsettings.json file as follows:

  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Debug",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    },

Set up logging for JwtBearer events

Here's the sample Program.cs file that shows how to set up logging for the preceding events:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
using Microsoft.IdentityModel.Logging;
using System.Diagnostics;
using Serilog;


// https://github.com/datalust/dotnet6-serilog-example

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();

Log.Information("starting up");
try
{
    var builder = WebApplication.CreateBuilder(args);

    builder.Host.UseSerilog((ctx, lc) => lc
        .WriteTo.Console()
        .ReadFrom.Configuration(ctx.Configuration));

    // Add services to the container.
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

    // Enable PII for logging
    IdentityModelEventSource.ShowPII = true;
    // Configure middleware events
    builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            ValidAudiences = new List<string> { "api://<application-client-id>", "<application-client-id>" },
            ValidIssuers = new List<string> { "https://sts.windows.net/<tenant ID>/", "https://login.microsoftonline.com/<tenant ID>/v2.0" }
        };
        options.Events = new JwtBearerEvents
        {
            OnTokenValidated = ctx =>
            {
                string message = "[OnTokenValidated]: ";
                message += $"token: {ctx.SecurityToken.ToString()}";
                Log.Information(message); 
                return Task.CompletedTask;
            },
            OnMessageReceived = ctx =>
            {
                string message = "[OnMessageReceived]: ";
                ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken);
                if (BearerToken.Count == 0)
                    BearerToken = "no Bearer token sent\n";
                message += "Authorization Header sent: " + BearerToken + "\n";
                Log.Information(message);
                return Task.CompletedTask;
            },
            OnAuthenticationFailed = ctx =>
            {
                ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
                string message = $"[OnAuthenticationFailed]: {ctx.Exception.ToString()}";
                Log.Error(message);
                // Debug.WriteLine("[OnAuthenticationFailed]: Authentication failed with the following error: ");
                // Debug.WriteLine(ctx.Exception);
                return Task.CompletedTask;
            },
            OnChallenge = ctx =>
            {
                // Debug.WriteLine("[OnChallenge]: I can do stuff here! ");
                Log.Information("[OnChallenge]");
                return Task.CompletedTask;
            },
            OnForbidden = ctx =>
            {
                Log.Information("[OnForbidden]");
                return Task.CompletedTask;
            }
        };
    });

    builder.Services.AddControllers();
    // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
    // builder.Services.AddEndpointsApiExplorer();
    // builder.Services.AddSwaggerGen();

    var app = builder.Build();

    app.UseSerilogRequestLogging();
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        // app.UseSwagger();
        // app.UseSwaggerUI();
        // do something
    }

    app.UseHttpsRedirection();

    app.UseAuthentication();
    app.UseAuthorization();

    app.MapControllers();

    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Unhandled exception");
}
finally
{
    Log.Information("Shut down complete");
    Log.CloseAndFlush();
}

References

Tutorial: Build and secure an ASP.NET Core web API with the Microsoft identity platform

Third-party information disclaimer

The third-party products that this article discusses are manufactured by companies that are independent of Microsoft. Microsoft makes no warranty, implied or otherwise, about the performance or reliability of these products.

Contact us for help

If you have questions, you can ask Azure community support. You can also submit product feedback to Azure feedback community.