Reputation: 3320
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
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
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.
enable both Anonymous and windows authentication in launchingsettings when debug. in iis if deployed on the server.
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();
standard set up in startup configureservices to add jwt authentication. in configure function, add authentication.
decorate your function that you want to use windows authentication with this:
[Authorize(AuthenticationSchemes = NegotiateDefaults.AuthenticationScheme)]
[Route("GetToken")]
[HttpGet]
public IActionResult GetToken()
{}
[Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]
[ApiController]
[Route("[controller]")]
public class ReportController : ControllerBase
{
}
that's all.
Upvotes: 6
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
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 :-
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
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
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
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