Skip to content

Commit 6b02ace

Browse files
authored
Merge pull request #8821 from genlin/main4677
AB#4677 unauthorized-aspnet-core-web-api.md
2 parents ee52ba6 + 7369455 commit 6b02ace

3 files changed

Lines changed: 186 additions & 0 deletions

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
---
2+
title: Troubleshooting 401 Unauthorized Errors in ASP.NET Core Web API with Microsoft Entra ID Authentication
3+
description: Provides guidance for troubleshooting and resolving 401 Unauthorized errors in an ASP.NET Core Web API that uses Microsoft Entra ID authentication.
4+
ms.date: 04/28/2025
5+
ms.author: bachoang
6+
ms.service: entra-id
7+
ms.custom: sap:Developing or Registering apps with Microsoft identity platform
8+
---
9+
10+
# 401 Unauthorized errors in ASP.NET Core Web API with Microsoft Entra ID
11+
12+
When you call an ASP.NET Core Web API that's secured by using Microsoft Entra ID authentication, you might encounter a "401 Unauthorized" error. This article provides guidance for using `JwtBearerEvents` to capture detailed logs to troubleshoot these errors.
13+
14+
## Symptoms
15+
16+
You use the `[Authorize]` attribute to [secure your ASP.NET Core Web API](/entra/identity-platform/tutorial-web-api-dotnet-core-build-app?tabs=workforce-tenant), as follows:
17+
18+
```csharp
19+
[Authorize]
20+
public class MyController : ControllerBase
21+
{
22+
...
23+
}
24+
25+
```
26+
27+
Or
28+
29+
```csharp
30+
31+
public class MyController : ControllerBase
32+
{
33+
[Authorize]
34+
public ActionResult<string> Get(int id)
35+
{
36+
return "value";
37+
}
38+
...
39+
}
40+
```
41+
42+
When you call the web API, a "401 Unauthorized" response is returned, but the message contains no error details.
43+
44+
## Cause
45+
46+
The API might return a "401 Unauthorized" response in the following scenarios:
47+
48+
- The request doesn't include a valid "Authorization: Bearer" token header.
49+
- The token is expired or incorrect:
50+
- The token is issued for a different resource.
51+
- The token claims don't meet the application's token validation criteria, as defined in the [JwtBearerOptions.TokenValidationParameters](/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbeareroptions.tokenvalidationparameters) class.
52+
53+
## Solution
54+
55+
To debug and resolve "401 Unauthorized" errors, use the `JwtBearerEvents` callbacks to capture and log detailed error information. Follow these steps to implement a custom error-handling mechanism.
56+
57+
The `JwtBearerEvents` class has the following callback properties (invoked in the following order) that can help you to debug these "401 Access Denied" or "UnAuthorization" issues:
58+
59+
- [`OnMessageRecieved`](/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbearerevents.onmessagereceived#Microsoft_AspNetCore_Authentication_JwtBearer_JwtBearerEvents_OnMessageReceived) is called first for every request.
60+
- [`OnAuthenticationFailed`](/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbearerevents.onauthenticationfailed) is called if the token doesn't pass the application's token validation criteria.
61+
- [`OnChallenge`](/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbearerevents.onchallenge) is called last before a "401" response is returned.
62+
63+
### Step 1: Enable PII logging
64+
65+
By default, personally identifiable information (PII) logging is disabled. Enable it in the **Configure** method of the Startup.cs file for debugging.
66+
67+
> [!Caution]
68+
> Use 'Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true' only in a development environment for debugging. Do not use it in a production environment.
69+
70+
```csharp
71+
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
72+
{
73+
if (env.IsDevelopment())
74+
{
75+
app.UseDeveloperExceptionPage();
76+
}
77+
else
78+
{
79+
// The default HSTS value is 30 days. You might want to change this value for production scenarios. See https://aka.ms/aspnetcore-hsts.
80+
app.UseHsts();
81+
}
82+
// turn on PII logging
83+
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
84+
85+
app.UseHttpsRedirection();
86+
app.UseAuthentication();
87+
app.UseMvc();
88+
}
89+
```
90+
91+
### Step 2: Create a utility method to format exception messages
92+
93+
Add a method to format, and flatten any exception messages for better readability:
94+
95+
```csharp
96+
public static string FlattenException(Exception exception)
97+
{
98+
var stringBuilder = new StringBuilder();
99+
while (exception != null)
100+
{
101+
stringBuilder.AppendLine(exception.Message);
102+
stringBuilder.AppendLine(exception.StackTrace);
103+
exception = exception.InnerException;
104+
}
105+
return stringBuilder.ToString();
106+
}
107+
```
108+
109+
### Step 3: Implement JwtBearerEvents callbacks
110+
111+
Configure the `JwtBearerEvents` callbacks in the `ConfigureServices` method of *Startup.cs* to handle authentication events and log error details:
112+
113+
```csharp
114+
public void ConfigureServices(IServiceCollection services)
115+
{
116+
....
117+
.AddJwtBearer(options =>
118+
{
119+
options.Authority = "https://login.microsoftonline.com/<Tenant>.onmicrosoft.com";
120+
// if you intend to validate only one audience for the access token, you can use options.Audience instead of
121+
// using options.TokenValidationParameters which allow for more customization.
122+
// options.Audience = "10e569bc5-4c43-419e-971b-7c37112adf691";
123+
124+
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
125+
{
126+
ValidAudiences = new List<string> { "<Application ID URI>", "10e569bc5-4c43-419e-971b-7c37112adf691" },
127+
ValidIssuers = new List<string> { "https://sts.windows.net/<Directory ID>/", "https://sts.windows.net/<Directory ID>/v2.0" }
128+
};
129+
130+
options.Events = new JwtBearerEvents
131+
{
132+
OnAuthenticationFailed = ctx =>
133+
{
134+
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
135+
message += "From OnAuthenticationFailed:\n";
136+
message += FlattenException(ctx.Exception);
137+
return Task.CompletedTask;
138+
},
139+
140+
OnChallenge = ctx =>
141+
{
142+
message += "From OnChallenge:\n";
143+
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
144+
ctx.Response.ContentType = "text/plain";
145+
return ctx.Response.WriteAsync(message);
146+
},
147+
148+
OnMessageReceived = ctx =>
149+
{
150+
message = "From OnMessageReceived:\n";
151+
ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken);
152+
if (BearerToken.Count == 0)
153+
BearerToken = "no Bearer token sent\n";
154+
message += "Authorization Header sent: " + BearerToken + "\n";
155+
return Task.CompletedTask;
156+
},
157+
#For completeness, the sample code also implemented the OnTokenValidated property to log the token claims. This method is invoked when authentication is successful
158+
OnTokenValidated = ctx =>
159+
{
160+
Debug.WriteLine("token: " + ctx.SecurityToken.ToString());
161+
return Task.CompletedTask;
162+
}
163+
};
164+
});
165+
...
166+
}
167+
```
168+
169+
### Sample results
170+
171+
When you implement `JwtBearerEvents` callbacks, if a "401 Unauthorized" error occurs, the response output should include such details as the following example:
172+
173+
```Output
174+
OnMessageRecieved:
175+
176+
Authorization Header sent: no Bearer token sent.
177+
```
178+
179+
If you use the API development tool to debug the request, you should receive error details, as shown in the following screenshot.
180+
181+
:::image type="content" source="media/401-unauthorized-aspnet-core-web-api/wrong-token.png" alt-text="Screenshot of error details in the API development tool." lightbox="media/401-unauthorized-aspnet-core-web-api/wrong-token.png":::
182+
183+
[!INCLUDE [Azure Help Support](../../../includes/azure-help-support.md)]
160 KB
Loading

support/entra/entra-id/toc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
items:
5050
- name: Developing or registering apps
5151
items:
52+
- name: 401 Unauthorized errors in ASP.NET Core Web API
53+
href: app-integration/401-unauthorized-aspnet-core-web-api.md
5254
- name: Cookies are disabled error in MSAL.Net XBAP application
5355
href: app-integration/script-errors-running-msal-net-xbap-app.md
5456
- name: IDX10501 Error in ASP.NET Core with Azure B2C Custom Policy
@@ -67,6 +69,7 @@
6769
href: app-integration/confidential-client-application-authentication-error-aadsts7000218.md
6870
- name: Infinite sign-in loop issue with ASP.NET applications
6971
href: app-integration/asp-dot-net-application-infinite-sign-in-loop.md
72+
7073

7174
- name: Troubleshoot adding apps
7275
href: app-integration/troubleshoot-adding-apps.md

0 commit comments

Comments
 (0)