Jenan
Jenan

Reputation: 3320

How to combine the windows authentication and JWT with .Net Core 2.1

I have tried to use the windows authentication and JWT together with .NET Core 2.1.

I have following startup settings of the authentication:

services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,

                    ValidIssuer = "Test",
                    ValidAudience = "Test",
                    IssuerSigningKey = JwtSecurityKey.Create("677efa87-aa4d-42d6-adc8-9f866e5f75f7")
                };

                options.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = OnAuthenticationFailed
                };
            });

IIS settings:

"iisSettings": {
    "windowsAuthentication": true, 
    "anonymousAuthentication": true, 
    ..
  }

I have tried following code snippet to create the JWT token with windows authentication:

[Route("api/[controller]")]
    [ApiController]
    [Authorize(AuthenticationSchemes = "Windows")]
    public class AuthController : ControllerBase
    {
        [HttpPost("token")]
        public IActionResult Token()
        {
            //Setup claims
            var claims = new[]
            {
                new Claim(ClaimTypes.Name, User.Identity.Name),
                //Add additional claims
            };

            //Read signing symmetric key
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("677efa87-aa4d-42d6-adc8-9f866e5f75f7"));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            //Create a token
            var token = new JwtSecurityToken(
                issuer: "Test",
                audience: "Test",
                claims: claims,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);

            //Return signed JWT token
            return Ok(new
            {
                token = new JwtSecurityTokenHandler().WriteToken(token)
            });
        }
    }

And in another controller I need use only JWT authentication:

[Route("api/[controller]")]
    [ApiController]
    [Authorize(AuthenticationSchemes = "Bearer")]
    public class ProductController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            var userName = User.Identity.Name;

            var claims = User.Claims.Select(x => new { x.Type, x.Value });

            return Ok(new { userName, claims });
        }
    }

If the JWT token is expired then I correctly received the response code 401 but I still get the dialog in the browser for putting the credentials.

How can I configure the windows authentication only for a part when I want to create the JWT token and disable response which is responsible for showing the browser dialog with credentials? How to correctly combine these things?

Upvotes: 12

Views: 21827

Answers (7)

Angry Coder
Angry Coder

Reputation: 145

This is desired behaviour of IIS. As soon as the application returns 401, IIS append WWW-Authenticate to the header which is why we see browser dialog to enter credentials.

For more: https://github.com/aspnet/Security/issues/1853

To fix this behaviour, we need to change the status from 401 to something else so through client side framework (blazor or angular) we can redirect the user to the login module(that call Authenticate Endpoint to get token) instead of showing the credentials dialog.

The easiest way is to override the OnChallenge behaviour of JWT Bearer and change the status from 401 to 600 (or whatever you like).

services.AddAuthentication(options =>
{
  options.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
  options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddNegotiate()
 .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,
        options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = Configuration["Jwt:Issuer"],
                ValidAudience = Configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])),
                ClockSkew = TimeSpan.FromMinutes(25)
            };
            options.Events = new JwtBearerEvents();
            // override the challenge behaviour and change the status to 600
            options.Events.OnChallenge = context =>
            {

                context.HandleResponse();
                context.Response.StatusCode = (int)CustomHttpStatusCode.JwtTokenExpire;
                return Task.CompletedTask;
            };

        });

Upvotes: 2

wxm146
wxm146

Reputation: 179

Here is how I did it in .net 5 to use both Windows authentication and Jwt on a web api. I first let windows authentication get identity name, then check against a database table for authorization to get roles etc, then return a jwt token. the client web site uses the jwt token to access jwt protected resources.

  1. enable both Anonymous and windows authentication in launchingsettings when debug. in iis if deployed on the server.

  2. if you want to debug through Kestrel, at the time of this writing, in startup, DON'T use services.AddAuthentication(IISDefaults.AuthenticationScheme);
    instead, use

     services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
    
  3. standard set up in startup configureservices to add jwt authentication. in configure function, add authentication.

  4. decorate your function that you want to use windows authentication with this:

    [Authorize(AuthenticationSchemes = NegotiateDefaults.AuthenticationScheme)]
    [Route("GetToken")]
    [HttpGet]
    public IActionResult GetToken() 
    {}
  1. decorate your controller that you want to use jwt token with this:
    [Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]
    [ApiController]
    [Route("[controller]")]
    public class ReportController : ControllerBase
    {
    }

that's all.

Upvotes: 6

Peter Murdoch
Peter Murdoch

Reputation: 21

In .Net 5.0 I have been able to successfully handle Windows, OIDC, Cookie, and JWT Schemes in a single application. IIS/LaunchSettings.json configuration must designate both Anonymous and Windows authentication as true, because bearer authentication will fail without server configuraton for Anonymous and Windows authentication will not work without server configuration for Windows.

Begin with an AllowAnonymous api and issue a response redirect (redirect is key here to force the non-anonymous auth scheme to issue a challenge and in the case of windows seamlessly receive the desired negotiate authorization header) to whichever additional routes you need to support, decorating them with the appropriate authorization scheme attribute.

I prefer to harvest the pertinent data from each non-jwt scheme, generate a new claims identity with the token as a claim and have all other routes aside from the authentication controller used by the application utilize the bearer scheme. I found it necessary to override both the AllowAnonymous and Authorize attribute on authorization implementations in order to ensure that all requests are always assured to be translated into ClaimsIdentity's with a valid bearer token to achieve consistency across all of the contexts.

To support an anonymous context for windows authenticated contexts, I offer a header that governs whether to try and seamlessly authenticate or otherwise simply geneate the ClaimsIdentity as an anonymous user. Take care to set the authentication type as an empty string when initializing a claims identity to accomplish this.

Upvotes: 2

Pooja Suryawanshi
Pooja Suryawanshi

Reputation: 341

To work with both windows and JWT bearer authentication-- windows authentication by default get applied to all pages and it over ride the functionality of JWT Bearer. For combining both into one single application :-

  1. Apply Windows authentication on the provider which are used to generate Token of JWt Bearer using [AllowAnonymous] tag on it, which is using windows authentication by default
  2. Apply Jwt Bearer authentication on rest of pages in the application using [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] on it

So, In above case we are using windows authentication for generating JWT Token and using that JWT Token we are authenticating rest of pages in application.

For Testing scenario, You can try postman with NTLM(windows authentication) for token generation controller and BearerToken for page which are having JWTAuthentication

Upvotes: 8

Stephan Steiner
Stephan Steiner

Reputation: 1295

The only way I found to get this done so far is using Kestrel. It allows me to control which authentication schemes are sent for every request. On IIS, I only control whether Bearer is sent or not, NTLM and Negotiate are sent by default as pointed out above.

Upvotes: 0

Todd Skelton
Todd Skelton

Reputation: 7239

The way I would handle this is to create two different web applications: one for Windows Authentication and one that uses JWT Token Authentication.

The Windows Authentication web application would be very small and only does one thing. Authenticate the user via Windows Authentication at an endpoint and return a JWT Token.

Then, that token can be used for the main application. As long as your signing key and audience is the same, it doesn't matter if the token is created on a different web application.

You won't need to struggle with trying to handle both at the same time.

Upvotes: 13

Todd Skelton
Todd Skelton

Reputation: 7239

This answer might help: https://stackoverflow.com/a/51055082/1212994

You need to ensure, that you NOT setting Authorization: Bearer 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.

Upvotes: 0

Related Questions