'.NET Core WebAPI - Setting default authorization policy on Negotiate (Windows authentication) changes HttpContext.User type
If I setup Negotiate authentication (Windows/NTLM) in a .NET Core 5.0 or 3.1 WebAPI, and add a default authorization policy to require authenticated user, the type of HttpContext.User changes from System.Security.Principal.WindowsPrincipal to System.Security.Claims.ClaimsPrincipal.
I can successfully reproduce this issue by following the steps below.
Environment:
- Visual Studio 2019 version 16.10.3
- Windows 10 1909 (Build 18363.1621) on a corporate domain, using on-premise Active Directory
- .NET Core 5.0.7/.NET Core 3.1.16
- IIS Express using In Process hosting model
Steps to Reproduce:
Create a new "ASP.NET Core WebAPI" project targeting .NET Core 5.0 or .NET Core 3.1. For what it's worth, I used the new project wizard in Visual Studio, not the
dotnetcommand.Modify
launchSettings.jsonto set IIS Express to enable Windows Authentication, and disable Anonymous Authentication. Also note I have disabled SSL by setting the port number to zero (not sure if this is relevant):{ "iisSettings": { "windowsAuthentication": true, "anonymousAuthentication": false, "iisExpress": { "applicationUrl": "http://localhost:46007", "sslPort": 0 } }, "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "weatherforecast", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }Add the "Microsoft.AspNetCore.Authentication.Negotiate" NuGet package
dotnet add package Microsoft.AspNetCore.Authentication.Negotiate(or with .NET Core 3.1:dotnet add package Microsoft.AspNetCore.Authentication.Negotiate --version 3.1.16)In
Startup.cs, add the followingusingstatement:using Microsoft.AspNetCore.Authentication.Negotiate;Add to
ConfigureServices()beforeservices.AddControllers();:services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) .AddNegotiate(); services.AddAuthorization();Before
app.UseAuthorization();inConfigure(), add:app.UseAuthentication();To test, I added an
[Authorize]attribute to the existingWeatherForecastController.csGet()method, then added a simple line of code to easily debug the HttpContext.User object. This is my code:using Microsoft.AspNetCore.Authorization; // code removed for brevity [Authorize] [HttpGet] public IEnumerable<WeatherForecast> Get() { // set a debug breakpoint to inspect this value var user = HttpContext.User; var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); }Start Visual Studio debugger with the IIS Express profile, then inspect the
HttpContext.Userobject. You will find the debugger reports this asSystem.Security.Principal.WindowsPrincipal, as expected.Modify
Startup.csto add the following default authorization policy toservices.AddAuthorization()so the[Authorize]attribute requires an authenticated user.using Microsoft.AspNetCore.Authorization;services.AddAuthorization(cfg => { cfg.DefaultPolicy = new AuthorizationPolicyBuilder(NegotiateDefaults.AuthenticationScheme) .RequireAuthenticatedUser() .Build(); });With no additional changes, repeat step 6. This time inspecting the same
HttpContext.Userobject, the debugger will report the type asSystem.Security.Claims.ClaimsPrincipal.
The issue
When the HttpContext.User is a WindowsPrincipal, you can use HttpContext.User.IsInRole(@"DOMAIN\Some_Group_Name"); to determine whether or not a user is a member of a specific Active Directory group, by name.
When HttpContext.User is a ClaimsPrincipal, the IsInRole() function always returns false for the same group name. Inspecting the claims, I do see where all the user's Active Directory groups are being pulled in, but can only be referenced by the SID, not the common name.
The only workaround I have found (Windows OS specific) is the following code:
new WindowsPrincipal((WindowsIdentity)HttpContext.User.Identity).IsInRole(@"DOMAIN\Some_Group_Name");
Why is this happening? Is this an issue with my configuration, code, and/or environment? How can I have a default authorization policy, but still have HttpContext.User as a WindowsPrincipal without the cast?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
