The typical scenario for creating a JWT would be to have an action in a controller that authenticates a user and returns a token. That token could then be used to authenticate the user for other actions. We’re going to need the help of some classes built into the framework.
using System; using System.Text; using System.Linq; using System.Threading.Tasks; using System.IdentityModel.Tokens.Jwt; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens;
Several classes need to be initialized via dependency injection, so here is the constructor for the controller.
public AuthController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, IPasswordHasher<IdentityUser> hasher, IConfiguration configuration, ILogger<AuthController> logger) { _signInManager = signInManager; _userManager = userManager; _hasher = hasher; _configuration = configuration; _logger = logger; }
With the groundwork set, let’s look at the function, that will create the JWT.
[ValidateModel] [HttpPost] public async Task<IActionResult> CreateToken([FromBody] CredentialModel model) { }
The action takes in a CredentialModel, which is just a class with two string properties (Username and Password). Both the properties are annotated with the [Required] attribute. The CreateToken action is decorated with the [ValidateModel], so the model can be checked for a valid model at the beginning of the function. If the model is not valid, we’ll just log it to the informational log, and return a bad request.
if(!ModelState.IsValid) { _logger.LogInformation($"Invalid CredentialModel passed to CreateToken (UserName:{model.UserName} Password:{model.Password}"); return BadRequest("Failed to create token."); }
The next step is to authenticate the user with the UserManager from Microsoft.AspNetCore.Identity. One option would be to use an instance of SignInManager
try { var user = await _userManager.FindByNameAsync(model.UserName); if(user != null) { if (_hasher.VerifyHashedPassword(user, user.PasswordHash, model.Password) == PasswordVerificationResult.Success) { //Create Token } } } catch (Exception ex) { _logger.LogError($"Exception thrown while creating JWT: {ex}"); }
Creating the token
Now let’s work on creating the token.
var userClaims = await = _userManager.GetClaimsAsync(user); var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Email, user.Email) }.Union(userClaims); var key = new SymetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _configuration["Tokens:Issuer"], audience: _configuration["Tokens:Audience"], claims: claims, expires: DateTime.UtcNow.AddMinutes(15), signingCredentials: creds); return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token), expiration = token.ValidTo });
Remember that the payload of the JWT is made up of claims, so the first step is to build up a set of claims for the user. The UserManager will return an array of claims, and we can merge those with some commonly used Registered Claims. For example, Jti is the JWT ID claim that provides a unique identifier to prevent replay attacks.
The next step is to create a key and signing credentials. The key is created based on a string found in the config file. Getting this from a key store, or some other method is a MUCH better idea.
Microsoft uses the JwtSecurityToken class to represent JWTs. The constructor takes the elements of a JWT that were mentioned in part 1. Once we have the token, we can return it from the function.
In the last part of this series, I’ll tell you how you can consume the JWT as part of a REST service.