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