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

11,172

Solution 1

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.

Solution 2

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

Solution 3

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.

Solution 4

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.

Share:
11,172
Jenan
Author by

Jenan

Updated on June 04, 2022

Comments

  • Jenan
    Jenan almost 2 years

    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?