I am trying to migrate an old OWIN self hosted WebApi that used this sort of thing
var listener = (HttpListener)appBuilder.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
To a new .NET Core 3.1 project. I have read about Auth
This is what my project file looks like
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Negotiate" Version="3.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.Text.Json" Version="4.7.0" />
</ItemGroup>
</Project>
And this is what my launchsettings.json
looks like
{
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:9600",
"sslPort": 0
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/audit",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"use64Bit": true
},
"Audit.Core": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/audit",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:9600"
},
"Azure Dev Spaces": {
"commandName": "AzureDevSpaces",
"launchBrowser": true
}
}
}
And this is what my Startup.cs
looks like
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.AspNetCore.Server.IISIntegration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NLog;
namespace xxxx
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
services.AddControllers().AddNewtonsoftJson();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
And here is the Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseHttpSys(options =>
{
options.Authentication.Schemes = AuthenticationSchemes.NTLM;
options.EnableResponseCaching = false;
options.Authentication.AllowAnonymous = false;
});
})
.ConfigureWebHost(config =>
{
config.UseUrls("http://*:9600");
});
}
And I have a custom filter that I need to grab the current users Identity from and verify it has a certain ActiveDirectory group associated with it. The basic usage is like this
[ApiController]
[Route("api/some")]
[ControllerExceptionFilter]
public class SomeController : ControllerBase
{
[HttpPost("add")]
[ActiveDirectoryAuthorize("SomeGroup")]
public IActionResult Add([FromBody]SomeEvent s)
{
var user = this.HttpContext.User.Identity;
return Ok("cool");
}
}
Where the filter looks like this
//https://stackoverflow.com/questions/31464359/how-do-you-create-a-custom-authorizeattribute-in-asp-net-core
public class ActiveDirectoryAuthorizeAttribute : TypeFilterAttribute
{
public ActiveDirectoryAuthorizeAttribute(string groupMembership) : base(typeof(ActiveDirectoryAuthorizeFilter))
{
Arguments = new object[] { groupMembership };
}
}
public class ActiveDirectoryAuthorizeFilter : IAuthorizationFilter
{
private string _groupMembership;
public ActiveDirectoryAuthorizeFilter(string groupMembership)
{
_groupMembership = groupMembership;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
try
{
Authenticate(context);
}
catch (InvalidWindowsUserException ex)
{
HandleUnauthorizedRequest(context);
}
catch (Exception ex)
{
HandleInternalServerError(context);
}
}
protected void HandleUnauthorizedRequest(AuthorizationFilterContext context)
{
context.HttpContext.Response.Headers.Add("WWW-Authenticate", "NTLM");
context.Result = new ContentResult
{
Content = "Unauthorized",
StatusCode = (int)HttpStatusCode.Unauthorized
};
}
private void HandleInternalServerError(AuthorizationFilterContext context)
{
context.HttpContext.Response.Headers.Add("WWW-Authenticate", "NTLM");
context.Result = new ContentResult
{
Content = "Internal Server Error",
StatusCode = (int)HttpStatusCode.InternalServerError
};
}
private void Authenticate(AuthorizationFilterContext context)
{
var identity = context?.HttpContext?.User?.Identity;
if (identity == null)
{
throw new InvalidWindowsUserException("Access denied");
}
EnsureAdmin(identity);
}
private void EnsureAdmin(IIdentity identity)
{
......
}
}
So with all this in place I can launch POSTMAN and issue a NTLM request with a BAD password, I get a 401. Which is expected
So then I edit the POSTMAN request to put in the right password, and I get this, where I get a 200 and get the "cool" response from the controller shown above
All working as expected so far. But if I then change the current working POSTMAN (200 ok) request to use a BAD NTLM password again. I was expecting to see a 401, but instead the current user just shows up as being still authorized in my custom filter
This behaviour is different to the OLD OWIN based WebApi. Which did recognize the following sequence
- NTLM bad password, 401 Unauthorized
- NTLM good password, 200 OK
- NTLM bad password, 401 Unauthorized
Is there something else I need to be setting somewhere? Does anyone have any clues on this?