3

We would like to use Kestrel to host our web-api. We must support both NTLM and Negotiate authentication.

That should be possible with Core 3.0 https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.0&tabs=visual-studio

However when Kestrel responds to a challange only Negotiate scheme is returned. Has anyone managed to implement NTLM authentication with Kestrel?

The application runs on a Windows 10 machine

Basically we have followed the recommendations. First added Authentication to services:

        services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();

and then added authentication to the pipeline

        app.UseAuthentication();

Also in the pipeline we have our own middleware to ensure user has been validated

        app.UseMiddleware<ValidateAuthentication>();

Implementation looks like this

internal class ValidateAuthentication : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        if (context.User.Identity.IsAuthenticated)
            await next(context);
        else
            await context.ChallengeAsync();
    }
}

Problem is that the challange response only has Negotiate

    WWW-Authenticate Negotiate

I would have expected both NTLM and Negotiate

    WWW-Authenticate NTLM, Negotiate
Torben Nielsen
  • 663
  • 1
  • 8
  • 21

3 Answers3

2

You can override the HandleChallengeAsync method and then replace the handler:

public sealed class NtlmNegotiateHandler : NegotiateHandler
{
    public NtlmNegotiateHandler(
        IOptionsMonitor<NegotiateOptions> options, 
        ILoggerFactory logger, UrlEncoder encoder, 
        ISystemClock clock) : base(options, logger, encoder, clock)
    {
    }

    protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        await base.HandleChallengeAsync(properties);

        if (Response.StatusCode ==  StatusCodes.Status401Unauthorized)
        {
            Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.WWWAuthenticate, "NTLM");
        }
    }
}
public sealed class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddAuthentication(NegotiateDefaults.AuthenticationScheme)
            .AddNegotiate();

        // replace the handler
        var serviceDescriptor = new ServiceDescriptor(typeof(NegotiateHandler), 
                                                      typeof(NtlmNegotiateHandler), 
                                                      ServiceLifetime.Transient);

        services.Replace(serviceDescriptor);
    }
}
Andriy Tolstoy
  • 5,690
  • 2
  • 31
  • 30
  • Thanks Andriy The challenge looks correct now. And with a correct challange my client will rspond with a new request including NTLM token. Only the service can still not authenticate. I supposw I would have to override HandleAuthenticateAsync as well. Only I have no idea how to authenticate a NTLM token? – Torben Nielsen Oct 10 '19 at 11:33
1

For .NET 6

builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
app.UseAuthorization();
app.MapControllers().RequireAuthorization();

or instead of requiring auth for all controllers you can add the [Authorize] annotation to your Controller classes.

Michael
  • 2,825
  • 3
  • 24
  • 30
0

For those interested, we have this working now, based on Michael's response

There is a catch however. Kestrel will not initiate a challange response on its own. We build our own with code similar to this

public class ValidateAuthentication : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        if (context.Request.Method == "OPTIONS")
        {
            await next(context);
            return;
        }

        if (context.User.Identity != null && context.User.Identity.IsAuthenticated)
        {
            await next(context);
        }
        else
        {
            context.Response.StatusCode = 401;
            context.Response.Headers["Proxy-Authenticate"] = "Negotiate";
            context.Response.Headers["WWW-Authenticate"] = "Negotiate";
            context.Response.Headers["Access-Control-Allow-Origin"] = context.Request.Headers["Origin"];
        }
    }
}
Torben Nielsen
  • 663
  • 1
  • 8
  • 21