.net core 2.0 JWT token
How to keep the JWT token valid until the user is logged-In in the app
without ask him/her again the login credentials to give him/her
another valid JWT token?
You can set a token expiry date and keep track of that.
How to make the JWT token invalid after the user decide to logout from the app?
- If you keep the expiry time short and keep refreshing the token expiry until the user logs out.
- You can save some kind of blacklist of invalid tokens so you can validate against that.
Update:
The JWT consists of the Header, Payload and Signature. You can read all about it here In the payload you can set an claim called: "exp".
The docs:
The "exp" (expiration time) claim identifies the expiration time on
or after which the JWT MUST NOT be accepted for processing. The
processing of the "exp" claim requires that the current date/time
MUST be before the expiration date/time listed in the "exp" claim.
Also, while researching to clarify my answer I found this SO answer: JSON Web Token expiration.
ASP.Net Core 2.0 mixed authentication of JWT and Windows Authentication doesn't accept credentials
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":
- You navigate to
http://example.com/api/resource
in your browser; - Your browser send a HTTP GET request to
http://example.com/api/resource
without anyAuthorization
HTTP Header for now (an anonymous request); - Web server (or WebAPI themself) recieve a request, find out, that there is no
Authorization
header and respond with401 Not Authorized
status code withWWW-Authenticate: NTLM,Negotiate
HTTP Header setted up ("Go away, no anonymous access. Only 'NTLM' or 'Negotiate' guys are welcome!"); - Browser receive a
401
response, find out that request was anonymous, looks toWWW-Authenticate
header and instantly repeat request, now withAuthorization: NTLM <NTLM_token>
HTTP Header ("Ok, take it easy, mr. Web server! Here is my NTLM token."); - 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:
- Your JS require
http://example.com/api/resource
with JWT authorization; - Your browser send a HTTP GET request to
http://example.com/api/resource
withAuthorization: Bearer <JWT_token>
HTTP Header now; - Web server (or WebAPI themself) recieve a request, find out, that there is
Authorization
header with "Bearer" authentication scheme and again respond with401 Not Authorized
status code withWWW-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!"); - Browser receive a
401
response, find out that request was authorized and decide that this token is bad. But, as you actually setAuthorization
header, this means that you actually have some credentials. And so it ask you for this credentials with this dialog.
JWT on .NET Core 2.0
Here is a full working minimal sample with a controller. I hope you can check it using Postman or JavaScript call.
appsettings.json, appsettings.Development.json. Add a section. Note, Key should be rather long and Issuer is an address of the service:
...
,"Tokens": {
"Key": "Rather_very_long_key",
"Issuer": "http://localhost:56268/"
}
...!!! In real project, don't keep Key in appsettings.json file. It should be kept in Environment variable and take it like this:
Environment.GetEnvironmentVariable("JWT_KEY");
UPDATE: Seeing how .net core settings work, you don't need to take it exactly from Environment. You may use setting. However,instead we may write this variable to environment variables in production, then our code will prefer environment variables instead of configuration.
AuthRequest.cs : Dto keeping values for passing login and password:
public class AuthRequest
{
public string UserName { get; set; }
public string Password { get; set; }
}Startup.cs in Configure() method BEFORE app.UseMvc() :
app.UseAuthentication();
Startup.cs in ConfigureServices() :
services.AddAuthentication()
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
});Add a controller:
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly IConfiguration _config;
private readonly IUserManager _userManager;
public TokenController(IConfiguration configuration, IUserManager userManager)
{
_config = configuration;
_userManager = userManager;
}
[HttpPost("")]
[AllowAnonymous]
public IActionResult Login([FromBody] AuthRequest authUserRequest)
{
var user = _userManager.FindByEmail(model.UserName);
if (user != null)
{
var checkPwd = _signInManager.CheckPasswordSignIn(user, model.authUserRequest);
if (checkPwd)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_config["Tokens:Issuer"],
_config["Tokens:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}
}
return BadRequest("Could not create token");
}}
That's all folks! Cheers!
UPDATE: People ask how get Current User. Todo:
In Startup.cs in ConfigureServices() add
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In a controller add to constructor:
private readonly int _currentUser;
public MyController(IHttpContextAccessor httpContextAccessor)
{
_currentUser = httpContextAccessor.CurrentUser();
}Add somewhere an extension and use it in your Controller (using ....)
public static class IHttpContextAccessorExtension
{
public static int CurrentUser(this IHttpContextAccessor httpContextAccessor)
{
var stringId = httpContextAccessor?.HttpContext?.User?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
int.TryParse(stringId ?? "0", out int userId);
return userId;
}
}
How do you implement JWT authentication in .NET Core 2.0?
in Startup.cs -> ConfigureServices, add in the following, updating with your settings:
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Audience = "your audience";
options.ClaimsIssuer = "claims issuer";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "claims issuer",
IssuerSigningKey = { your signing key }
};
});
Next up, in Configure:
app.UseAuthentication();
app.UseMvc();
You have to make sure that UseAuthentication is BEFORE UseMvc for it to work.
Basically everything that was previously in the Configure method has now been moved to the Services section :)
Check out this blog post for an example, as well as a link to a repo where you can get the example project: Shawn Wildermuth's Blog
Core 2.0 API Auth with JWT returns unauthorized
I used JWT authentication in my one of project. I would like to show my implementation, maybe this will help you. But probably you forget to add UseAuthentication(); into configure method in startup class.
startup.cs
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
var appSettings = Configuration.GetSection("AppSettings");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}
)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = appSettings["JwtAudience"],
ValidateIssuer = true,
ValidIssuer = appSettings["JwtIssuer"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appSettings["JwtSigningKey"]))
};
});
}
generateToken method
private string GenerateToken(string email)
{
SecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Value.JwtSigningKey));
var token = new JwtSecurityToken(
issuer: _appSettings.Value.JwtIssuer,
audience: _appSettings.Value.JwtAudience,
claims: new[]
{
new Claim(JwtRegisteredClaimNames.UniqueName, email),
new Claim(JwtRegisteredClaimNames.Email, email),
new Claim(JwtRegisteredClaimNames.NameId, Guid.NewGuid().ToString())
},
expires: DateTime.Now.AddMinutes(_appSettings.Value.JwtExpireMinute),
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Related Topics
High Memory Consumption with Enumerable.Range
C# Generic Type Inference with Multiple Types
Why Doesn't Mutex Get Released When Disposed
What Is Meant by "Managed" VS "Unmanaged" Resources in .Net
Methodinvoker VS Action for Control.Begininvoke
How to Get the Available Wifi Aps and Their Signal Strength in .Net
String Format Numbers Thousands 123K, Millions 123M, Billions 123B
Create a .Txt File If Doesn't Exist, and If It Does Append a New Line
Azure Shared Access Signature - Signature Did Not Match
Convert String to Int Array Using Linq
Incorrect Stacktrace by Rethrow
Argumentoutofrangeexception on Initialized List
How Can One Generate and Save a File Client Side Using Blazor
How to Check If a Property Exists on a Dynamic Anonymous Type in C#