Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.Net Core 2.0 mixed authentication of JWT and Windows Authentication doesn't accept credentials

Tags:

I've API created in asp.net core 2.0 where I am using mixed mode authentication. For some controllers JWT and for some using windows authentication.

I've no problem with the controllers which authorize with JWT. But for the controllers where I want to use windows authentication I am indefinitely prompted with user name and password dialog of chrome.

Here my sample controller code where I want to use Windows Authentication instead of JWT.

[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "Windows")]
public class TestController : Controller
{
    [HttpPost("processUpload")]
    public async Task<IActionResult> ProcessUploadAsync(UploadFileModel uploadFileModel)
    {

    }
}

My configure services code

public void ConfigureServices(IServiceCollection services)
{
     services.AddAuthentication(options =>
     {
        options.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
     })
     .AddJwtBearer("Bearer", options =>
     {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,       
            ValidateIssuer = false,  
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("blahblahKey")),
            ValidateLifetime = true, //validate the expiration and not before values in the token
            ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
        };
     });

     // let only my api users to be able to call 
     services.AddAuthorization(auth =>
     {
        auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
            .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
            .RequireClaim(ClaimTypes.Name, "MyApiUser").Build());
     });

     services.AddMvc();
}

My configure method.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseCors("CorsPolicy");

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseAuthentication(); //needs to be up in the pipeline, before MVC
    app.UseMvc();
}

Appreciate your suggestions and help on this.

Update: Till now I've been debugging my code on chrome. But when I have used IE 11, the above code is running without any issue.

Can this be CORS issue of chrome where preflight issue?

Thanks

like image 826
coolcake Avatar asked Jun 27 '18 01:06

coolcake


People also ask

How do I enable Windows Authentication in web config?

On the taskbar, click Start, and then click Control Panel. In Control Panel, click Programs and Features, and then click Turn Windows Features on or off. Expand Internet Information Services, then World Wide Web Services, then Security. Select Windows Authentication, and then click OK.

How do I turn off Windows Authentication in web config?

Expand the computer name, then Sites, then Default Web Site, then click on the name of the desired site. Select Authentication. Set Windows Authentication to Disabled and set Basic Authentication to Enabled.


2 Answers

You need to ensure, that you NOT setting Authorization: Bearer <JWT_token> HTTP header when you trying to use Windows Auth. The key point here is how "Windows Auth" actually works. Let's look how it works with browser for example.

Let's call this "a normal flow":

  1. You navigate to http://example.com/api/resource in your browser;
  2. Your browser send a HTTP GET request to http://example.com/api/resource without any Authorization HTTP Header for now (an anonymous request);
  3. Web server (or WebAPI themself) recieve a request, find out, that there is no Authorization header and respond with 401 Not Authorized status code with WWW-Authenticate: NTLM,Negotiate HTTP Header setted up ("Go away, no anonymous access. Only 'NTLM' or 'Negotiate' guys are welcome!");
  4. Browser receive a 401 response, find out that request was anonymous, looks to WWW-Authenticate header and instantly repeat request, now with Authorization: NTLM <NTLM_token> HTTP Header ("Ok, take it easy, mr. Web server! Here is my NTLM token.");
  5. Server receive a second request, find NTLM token in Authorization header, verify it and execute request ("Ok, you may pass. Here is your resource.").

Things goes a little different, when you initialy set Authorization header to some value:

  1. Your JS require http://example.com/api/resource with JWT authorization;
  2. Your browser send a HTTP GET request to http://example.com/api/resource with Authorization: Bearer <JWT_token> HTTP Header now;
  3. Web server (or WebAPI themself) recieve a request, find out, that there is Authorization header with "Bearer" authentication scheme and again respond with 401 Not Authorized status code with WWW-Authenticate: NTLM,Negotiate HTTP Header setted up ("Go away, we don't know who are this 'Bearer' guys, but we don't like them. Only 'NTLM' or 'Negotiate' guys are welcome!");
  4. Browser receive a 401 response, find out that request was authorized and decide that this token is bad. But, as you actually set Authorization header, this means that you actually have some credentials. And so it ask you for this credentials with this dialog.
like image 69
vasily.sib Avatar answered Sep 18 '22 22:09

vasily.sib


I just had the same need. I'm not yet running things on IIS, only Kestrel, but I managed to adapt Microsoft's own instructions to get per controller/controller method authentication using JWT and Windows auth.

All I did was modify Startup.cs/ConfigureServices from

services.AddAuthentication(x =>
{
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
    x.RequireHttpsMetadata = false; // should be set to true in production
    x.SaveToken = true;
    x.TokenValidationParameters = generateTokenValidationParameters();
});

to this

services.AddAuthentication()
.AddNegotiate()
.AddJwtBearer(x => 
{
    x.RequireHttpsMetadata = false; // should be set to true in production
    x.SaveToken = true;
    x.TokenValidationParameters = generateTokenValidationParameters();
});

So, basically, removed the Default Authentication and Challenge scheme, added Negotiate (Windows Auth) and JwtBearer using my pre-existing JWT configuration.

In the controllers, I enabled Windows Authentication by adding this authorization header

[Authorize(AuthenticationSchemes = NegotiateDefaults.AuthenticationScheme)]

And similarly, I replaced my existing Authorization headers that previously did JWD (given that it was the default auth/challenge scheme) with this

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

My app will be hosted by Kestrel, so IIS won't be an issue, but I'll try that next, anyway.

@edit: I've now mastered IIS Express, too. Enabled Windows Auth through VS's IIS Express settings (Project Properties, Debug tab), then ensure that IIS does not perform automatic authentication for in and out of processing hosting by adding this to Startup.ConfigureServices (right after AddAuthentication).

//disable automatic authentication for in-process hosting
services.Configure<IISServerOptions>(options => 
{
    options.AutomaticAuthentication = false;
});

//disable automatic authentication for out-of-process hosting
services.Configure<IISOptions>(options => 
{
    options.AutomaticAuthentication = false;
});

I then changed my test controller to have a method with the following authorize header

[Authorize(AuthenticationSchemes = IISDefaults.AuthenticationScheme)]

And when I access that method with a browser that trusts the URL, I'm being let in and User.Identity is my windows identity.

Now off to see if that also works on an actual IIS.

like image 32
Stephan Steiner Avatar answered Sep 20 '22 22:09

Stephan Steiner