Skip to content

Commit 751d2df

Browse files
authored
Convert blog post to article
1 parent ca3039a commit 751d2df

File tree

2 files changed

+177
-0
lines changed

2 files changed

+177
-0
lines changed
261 KB
Loading
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
title: Use Serilog to troubleshoot protected Web API authentication or authorization errors
3+
description: Provides a sample web API application to troubleshoot Microsoft Entra protected Web API authentication or authorization errors using Serilog logs.
4+
ms.date: 04/28/2025
5+
ms.service: entra-id
6+
ms.custom: sap:Developing or Registering apps with Microsoft identity platform
7+
ms.reviewer: bachoang, v-weizhu
8+
---
9+
# Use Serilog to troubleshoot Microsoft Entra protected Web API authentication or authorization errors
10+
11+
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](https://github.com/bachoang/Net6WebAPILogging) to set and collect logs for JwtBearer events.
12+
13+
## Net6WebAPILogging sample application
14+
15+
This sample web API application assumes you already have a web API application registered in Microsoft Entra ID. It uses Microsoft .NET 6 Framework and [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) NuGet package. For more information about how to protect an ASP.NET Core Web API application with Microsoft Entra ID, see [Tutorial: Build and secure an ASP.NET Core web API with the Microsoft identity platform](/entra/identity-platform/tutorial-web-api-dotnet-core-build-app).
16+
17+
It uses the following methods to set and collect logs for JwtBearer events:
18+
19+
- Use the [JwtBearerEvents class](/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbearerevents) to configure middleware events. JWT Bearer token might fail to validate `OnTokenValidated`, `OnMessageReceived`, `OnAuthenticationFailed`, and `OnChalleenge` events.
20+
- [Set up logging for JwtBearer events](#set-up-logging-for-jwtbearer-events).
21+
- Use the [Serilog](https://serilog.net/) framework to log the `Debug` output to the console window and the local file whose path is specified in the **appsettings.json** file.
22+
23+
## Run the sample application
24+
25+
To run the sample application, you must perform the following steps:
26+
27+
### Step 1: Configure the application ID URI for a protected web API
28+
29+
To add the application ID URI for a web API, follow these steps:
30+
31+
1. In the Azure portal, navigate to the app registration of the web API.
32+
2. Select **Expose an API** under **Manager**.
33+
3. At the top of the page, select **Add** next to **Application ID URI**. This defaults to `api://<application-client-id>`.
34+
4. Select **Save**.
35+
36+
:::image type="content" source="media/serilog-protected-web-api-authentication-authorization-errors/application-id-uri.png" alt-text="Screenshot that shows how to set the application ID URI in an app registration." border="false":::
37+
38+
### Step 2: Change the sample application configuration
39+
40+
Change the following information in the `AzureAd` section in the **appsettings.json** file with your own app registration information:
41+
42+
```json
43+
"AzureAd": {
44+
"Instance": "https://login.microsoftonline.com/",
45+
"Domain": "<tenant name>.onmicrosoft.com", // for example contoso.onmicrosoft.com
46+
"TenantId": "<tenant ID>",
47+
"ClientId": "<application-client-id>"
48+
},
49+
```
50+
51+
## Step 3: Change the sample application code
52+
53+
Change the `ValidAudiences` and `ValidIssuers` properties of the [TokenValidationParameters](/dotnet/api/microsoft.identitymodel.tokens.tokenvalidationparameters) class in the **Program.cs** file.
54+
55+
## Set up logging for JwtBearer events
56+
57+
Here's the sample Program.cs file that shows how to set up logging for the preceding events:
58+
59+
```csharp
60+
using Microsoft.AspNetCore.Authentication;
61+
using Microsoft.AspNetCore.Authentication.JwtBearer;
62+
using Microsoft.Identity.Web;
63+
using Microsoft.IdentityModel.Logging;
64+
using System.Diagnostics;
65+
using Serilog;
66+
67+
68+
// https://github.com/datalust/dotnet6-serilog-example
69+
70+
Log.Logger = new LoggerConfiguration()
71+
.WriteTo.Console()
72+
.CreateBootstrapLogger();
73+
74+
Log.Information("starting up");
75+
try
76+
{
77+
var builder = WebApplication.CreateBuilder(args);
78+
79+
builder.Host.UseSerilog((ctx, lc) => lc
80+
.WriteTo.Console()
81+
.ReadFrom.Configuration(ctx.Configuration));
82+
83+
// Add services to the container.
84+
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
85+
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
86+
87+
// Enable PII for logging
88+
IdentityModelEventSource.ShowPII = true;
89+
// Configure middleware events
90+
builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
91+
{
92+
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
93+
{
94+
ValidAudiences = new List<string> { "api://<application-client-id>", "<application-client-id>" },
95+
ValidIssuers = new List<string> { "https://sts.windows.net/<tenant ID>/", "https://login.microsoftonline.com/<tenant ID>/v2.0" }
96+
};
97+
options.Events = new JwtBearerEvents
98+
{
99+
OnTokenValidated = ctx =>
100+
{
101+
string message = "[OnTokenValidated]: ";
102+
message += $"token: {ctx.SecurityToken.ToString()}";
103+
Log.Information(message);
104+
return Task.CompletedTask;
105+
},
106+
OnMessageReceived = ctx =>
107+
{
108+
string message = "[OnMessageReceived]: ";
109+
ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken);
110+
if (BearerToken.Count == 0)
111+
BearerToken = "no Bearer token sent\n";
112+
message += "Authorization Header sent: " + BearerToken + "\n";
113+
Log.Information(message);
114+
return Task.CompletedTask;
115+
},
116+
OnAuthenticationFailed = ctx =>
117+
{
118+
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
119+
string message = $"[OnAuthenticationFailed]: {ctx.Exception.ToString()}";
120+
Log.Error(message);
121+
// Debug.WriteLine("[OnAuthenticationFailed]: Authentication failed with the following error: ");
122+
// Debug.WriteLine(ctx.Exception);
123+
return Task.CompletedTask;
124+
},
125+
OnChallenge = ctx =>
126+
{
127+
// Debug.WriteLine("[OnChallenge]: I can do stuff here! ");
128+
Log.Information("[OnChallenge]");
129+
return Task.CompletedTask;
130+
},
131+
OnForbidden = ctx =>
132+
{
133+
Log.Information("[OnForbidden]");
134+
return Task.CompletedTask;
135+
}
136+
};
137+
});
138+
139+
builder.Services.AddControllers();
140+
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
141+
// builder.Services.AddEndpointsApiExplorer();
142+
// builder.Services.AddSwaggerGen();
143+
144+
var app = builder.Build();
145+
146+
app.UseSerilogRequestLogging();
147+
// Configure the HTTP request pipeline.
148+
if (app.Environment.IsDevelopment())
149+
{
150+
// app.UseSwagger();
151+
// app.UseSwaggerUI();
152+
// do something
153+
}
154+
155+
app.UseHttpsRedirection();
156+
157+
app.UseAuthentication();
158+
app.UseAuthorization();
159+
160+
app.MapControllers();
161+
162+
app.Run();
163+
}
164+
catch (Exception ex)
165+
{
166+
Log.Fatal(ex, "Unhandled exception");
167+
}
168+
finally
169+
{
170+
Log.Information("Shut down complete");
171+
Log.CloseAndFlush();
172+
}
173+
```
174+
175+
[!INCLUDE [Azure Help Support](../../../includes/third-party-disclaimer.md)]
176+
177+
[!INCLUDE [Azure Help Support](../../../includes/azure-help-support.md)]

0 commit comments

Comments
 (0)