NTLM over Kestrel 401

Hello, I have a .net7 minimal api running over Kestrel in VS Code. I have negotiate authentication setup for windows auth and it works correctly when hitting any endpoints in the browser or when the app is deployed to IIS.

The problem is that when we try to use postman to hit the endpoints locally we always receive a 401 even with NTLM auth setup with the correct credentials. It doesnt even seem like it hits our middleware. I have SSL disabled in postman and no certificates set.

"https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": false,
      "applicationUrl": "https://localhost:7297;http://localhost:5209",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "windowsAuthentication": true,
      "anonymousAuthentication": false
    },
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
    .AddNegotiate();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});
app.UseAuthentication();
app.UseAuthorization();

Here is the console log in postman:

GET https://localhost:7297/GetStatusOptions4015 ms

Warning: Unable to verify the first certificate

  • :play_button:Network
  1. :play_button:addresses: {…}

  2. :play_button:local: {…}

1. address: "::1"

2. family: "IPv6"

3. port: 63028
  1. :play_button:remote: {…}
1. address: "::1"

2. family: "IPv6"

3. port: 7297
  1. :play_button:tls: {…}

  2. reused: false

  3. authorized: false

  4. authorizationError: “UNABLE_TO_VERIFY_LEAF_SIGNATURE”

  5. :play_button:cipher: {…}

1. name: "ECDHE-RSA-AES256-GCM-SHA384"

2. standardName: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"

3. version: "TLSv1/SSLv3"
  1. protocol: “TLSv1.2”

  2. ephemeralKeyInfo: {}

  3. :play_button:peerCertificate: {…}

1. ▶subject: {…}

  1. commonName: "localhost"

  2. alternativeNames: "DNS:localhost"
2. ▶issuer: {…}

  1. commonName: "localhost"
3. validFrom: "Dec 4 17:08:56 2024 GMT"

4. validTo: "Dec 4 17:08:56 2025 GMT"

5. fingerprint: "D2:E6:51:B1:70:A5:8C:CD:B8:E4:0A:60:CB:42:BD:CD:F6:99:C0:6C"

6. serialNumber: "8e8de005f9957524"

:play_button:Request Headers

User-Agent: PostmanRuntime/7.43.2

Accept: /

Cache-Control: no-cache

Postman-Token: 9ba8828a-84c7-40a1-940c-327102498669

Host: localhost:7297

Accept-Encoding: gzip, deflate, br

Connection: keep-alive

:play_button:Response Headers

Content-Length: 0

Date: Wed, 19 Mar 2025 14:37:24 GMT

Server: Kestrel

WWW-Authenticate: Negotiate

:play_button:Response Body

2 Likes

Hey Tyler, welcome to the Postman Community!

Thanks for sharing your question. While I don’t have a solution for you just yet, I wanted to acknowledge your issue and let you know that help is on the way soon. Our community is full of knowledgeable folks, so I’m sure someone will chime in with insights.

Appreciate you being here, and thanks for kicking off the conversation!

1 Like

Hey @trayford70. Welcome to the Postman Community :postman_logo:.

You will need to provide some more information to enable us understand the problem you’re experiencing better.

The console output you shared looks like a console output from your server. is this correct? A 401 status code is an authorization status code. Can you share how you’re managing your authorization in Postman? Postman has an NLTM authorization helper, are you using that to setup your authorization?

1 Like

Hello @gbadebo-bello!

That console output is from the Postman desktop client v11.37.1. I have NTLM authorization setup with my AD username and password. I have tried with and without specifying the domain and I have SSL turned off in the settings globally and in the request settings. My .NET API is running over Kestrel and I posted my auth code setup in the original post.

Based off the postman console, it seems that it is receiving the Negotiate response but it never responds the second time. I will try to upload as much info as I can in this post. Let me know if I have missed anything!



1 Like

@trayford70
Good Morning All,
I may see that your baseUrl is your localhost. Whether you can share your collection that will be helpful to check that unit test. The issue could be on your end. There is no information regarding your rooting or your method actions.

Thank you for sharing.

@jecorde

Hello, I also think it may be something on our end but I cannot figure out what it is. I’m not too familiar with how certificates work but my guess is its something to do with that. All I’ve done in my dev vm is generate a cert like so:

dotnet dev-certs https --trust

Unfortunately, I dont think I can share the collection for company reasons but yes the baseURL value is https://localhost:7297, which is correct.

As for the routing and method actions, is this what you are looking for? Ive stripped out what I dont think i can share but here is the setup for the API. Please let me know what other info I need to supply!

using Microsoft.AspNetCore.Server.Kestrel.Https;

var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;


builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowSpecificOrigin",
        builder =>
        {
            builder.WithOrigins(origins)
                .AllowAnyHeader()
                .AllowAnyMethod()
                .AllowCredentials();
        });
});

builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
    .AddNegotiate();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});


var app = builder.Build();


app.UseHttpsRedirection();
app.UseCors("AllowSpecificOrigin");
app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/GetStatusOptions", [Authorize] async (Context db, string stripped) =>
{
 //STRIPPED EF LINQ QUERY
});

app.Run();
1 Like

Thank you for your feedback
The page you are showing on is very likely the startup page.

The first thing that comes to mind is that the class HttpConfiguration is missing above.

It is common to separate the controllers and the startup page with the appropriate routing.

For instance, in the startup page I may write something like this:

config.Routes.MapHttpRoute(
name: “DefaultApi”,
routeTemplate: “api/{controller}/{id}”,
defaults: new { id = RouteParameter.
Optional}
);

The Get method is developed in the controller classes. So the syntax is similar to:

  [HttGet]
	public IHttpActionResult Get()
	{
		return Ok("Get");
	}

The default conventions for binding your code to an Http Request shall be reviewed.(HttpGet)

Whether you are able to share your collection or a sample, that will be helpful to check the response of your request.

Thank you very much.

Hello,

i think the problem is, that

Kestrel requires the Negotiate header prefix, it doesn’t support directly specifying NTLM in the request or response auth headers. NTLM is supported in Kestrel, but it must be sent as Negotiate .

as mentioned here.

But as far as I could find out, Postman only sends the NTLM header and there is no way to manually add the Negotiate header to satisfy Kestrel.

Best regards

1 Like

Thank you for your feedback. Keep going !

I appeared that you’re using visual studio code. Visual studio community is the free version of visual studio even though my favorite is Visual studio code. I hope you to keep up your endeavors.

Kestrel is an open source web server for ASP.Net core. Therefore you are using an open source tool or even a free tool to build WebApi with .Net

I shall confess that it is an interesting post.

NTLM is a suite of Microsoft security protocols intended to provide authentication, integrity and confidentiality to users.

Thank you for your time.

According to my knowledge in building WebApis through .Net , the security module and and the documentation came later. I believe you shall find a way to distinguish the controllers modules which are separated from NTLM. Postman shall operate for unit testing on both modules.

I believe you are performing a classic authorization through NTLM. Hereafter, you may delve on tokens.

In brief, if you’re able through VS Code to separate the startup, the controllers( I saw an Http verb Get) and the security module NTLM you shall get outcomes from Postman either with the controller or the security module throughout unit tests.

Whether you’re interested to invite me in your workspace I may have some time to share comments on your collections.

Again, thank you for that post.

We are also facing same issue, in our case we cannot change the other authentication type as most of our customer requirement is to use NTLM. We have good amount of postman api calls to validate our services but unable to use it for NTLM. it really helps if we are able to solve this issue.