Securing an API with JSON Web Tokens (Configuring the API)

To add JWT bearer authentication to an ASP.net Core site, you’ll need to add the JWTBearer middleware to the application. The middleware is found in the Microsoft.AspNetCore.Authentication.JwtBearer namespace, and will allow the service to validate a token, and create a ClaimsPrincipal with the claims in the token. To add the middleware to the service, we’ll start by adding authentication with the options DefaultAuthenticationScheme and DefaultChallengeScheme set to JwtBearerDefaults.AuthenticationScheme. So when the [Authorize] attribute is put on controllers or actions, they will default to use JwtBearer authentication. In the Startup.cs file, there should be a ConfigureServices function that provides you with a IServiceCollection interface to do the configuring.

public void ConfigureServices(IServiceCollection services)
{
     services.AddDbContext<RESTContext>();
     services.AddIdentity<IdentityUser, IdentityRole>()
          .AddEntityFrameworkStores<RESTContext>();

     services.AddAuthentication(options =>
     {
          options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
          options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
     })

...

}

JwtBearer Options

The next service we need to add is the JwtBearer service, which allows configuration with the JwtBearerOptions parameter. Here are a list of several of the options.

  • RequiredHttpsMetadata – JWT bearer tokens should never be passed around without using HTTPS. If someone were to intercept a token, they could use it to impersonate the owner of the token. Always set this option to true.
  • AutomaticAuthenticate – When set to true, it will cause the service to automatically authenticate the user defined in the token.
  • Audience – This value represents the service or resource that will receive the incoming token. Remember that list of registered claims that you can put in the payload of a token? Audience is one of them, and if the value in the token doesn’t match what is placed in this option, the token will be rejected.
  • Authority – The authority option is used to specify the address of the authentication server that issued the token. If you set this option, the middleware will use it to get the public key that is used to validate the token’s signature. It will also verify that the issuer claim found in the token matches this value.
     services.AddAuthentication(options =>
     {
          options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
          options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

     }).AddJwtBearer(options =>
     {
          options.Audience = Configuration["Tokens:Issuer"];
          options.RequireHttpsMetadata = true;  
          
       

Take note that I did not set the Authority option. The code sample is running a web application that issues and authenticates tokens that it creates. It is not reaching out to an authentication server to validate the token’s signature. If you were going to use a third party service to provide authentication, than this option becomes important.

Validation Options

We will also need to set the parameters that are used to validate a token. In ASP.net Core 2, this is done with the TokenValidationParameters object.
These are a list of the relevant options.

  • ValidateIssuer(s) – Do you want to validate the issuer of the token? Generally this gets set to true.
  • ValidIssuer – If you’re going to validate the issuer of the token, this is the value should match the issuer claim in the token.
  • ValidateIssuerSigningKey – Do you want to validate the Issuer’s signing key?
  • IssuerSigningKey – This is the public key used to validate JWT tokens. When you put the key here, it means that the service won’t need access to the token issuing authentication server. It could come from a file, certificate store, or as in the example below a configuration file. BTW don’t use the example below, it’s not your best security option.
     services.AddAuthentication(options =>
     {
          options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
          options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

     }).AddJwtBearer(options =>
     {
          options.Audience = Configuration["Tokens:Issuer"];
          options.RequireHttpsMetadata = true;  
          options.TokenValidationParameters = new TokenValidationParameters
          {
               ValidateIssuerSigningKey = true,
               ValidateIssuer = true,
               ValidIssuer = Configuration["Tokens:Issuer"],
               IssuerSigningKey = new SymetricSecurityKey(Encoding.UTF9.GetBytes(Configuration["Tokens:Key"])),
               ValidateLifetime = true
           };

Now the last thing we need to do here is handle the event that gets fired when authentication has failed.

               ValidateIssuer = true,
               ValidIssuer = Configuration["Tokens:Issuer"],
               IssuerSigningKey = new SymetricSecurityKey(Encoding.UTF9.GetBytes(Configuration["Tokens:Key"])),
               ValidateLifetime = true
           };
          options.Events = new JwtBearerEvents()
          {
               OnAuthenticationFailed = c =>
               {
                    c.NoResult();
                    c.Response.StatusCode = 500;
                    c.Response.ContentType = "text/plain";
                    return c.Response.WriteAsync("An error occurred processing your authentication.");
               }
          };

Applying the Attributes

Once the middleware is configured and added to the pipeline, you can use standard authorize attributes on your controllers.
You could also specify that a controller or action use JwtBearer authentication as part of the attribute if you need to be specific.

[Authorize(AuthenticationSchemess = JwtBearerDefaults.AuthenticationScheme)]

Securing an API with JSON Web Tokens (How Do I Create a JWT?)

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, and call the PasswordSignInAsync function. This function will check the user credentials, and create an authentication cookie. If you don’t want the cookie to be created, you can use the IPasswordHasher interface to verify the authentication credentials.

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.

Securing an API with JSON Web Tokens (What is a JWT)?

JSON Web Token, prounced “jot”, is an open standard that lets you securely transmit information as a JSON object. The token can be signed with a secret using HMAC or a public/private key using RSA. This makes JWTs a great option for authentication. Each JWT is comprised of three parts separated by dots, which are the header, payload, and signature.

Here is an example of a JWT.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Header

The header defines what kind of token it is, and the type of hashing algorithm that was used to create the token.

{
“alg”: “HS256”,
“typ”: “JWT”
}

The header is Base64URL encoded, which then becomes the first third of the JWT.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

The second part of the token is the payload, and contains claims and any other metadata about the user. In the example below, claims are included in the payload and then encrypted to create the payload.

{
“sub”: “1234567890”,
“name”: “John Doe”,
“admin”: true
}

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

The claims used in the payload are categorized into three groups.

Registered Claims are predefined claims that are defined in RFC 7519. An application should define which claims it uses, and when they are required or optional. The names of the claims are short, three letters, to keep the size of the token small.

  • “iss” : Issuer Claim – identifies the principal that issued the JWT.
  • “sub” : Subject Claim – identifies the principal that is the subject of the JWT.
  • “aud” : Audience Claim – identifies the recipients that the JWT is intended for.
  • “exp” : Expiration Time – identifies the expiration time on or after which the JWT must not be accepted.
  • “jti” : JWT ID Claim – provides a unique identifier for the JWT. The value can be used to prevent the JWT from being replayed.

Public Claims are defined by who ever is creating the token. The concern here is to choose a name that is not likely to collide with another claim name. Ideally, they should be registered in the IANA “JSON Web Token Claims” registry.

Private Claims are agreed to be used by the who ever produced the token, and those who consume the token. The names are not registered, and are likely to be subject to collisions.

Signature

The purpose of the signature is to verify that the sender is who they say they are, and to ensure that the token was not altered in transmission. The signature is created by combining the encoded header, the encoded payload, and a secret. The information is then encrypted.

How Are JWTs Used To Authenticate Users

Traditionally when a user logs into a server, the service will create a session in the server, and return a cookie. JWTs work a bit differently, because when the service authenticates a consumer, it will create the JWT and return it to the consumer, where it is stored locally. On subsequent calls to the service, the consumer includes the JWT in the Authorization header using the Bearer schema.

Authorization: Bearer

There are multiple benefits to this method. The authentication method is stateless, because there is no need to maintain a session on the server. Furthermore, the JWTs are self-contained, and don’t require additional calls to a database to validate the credentials. If you have a service that makes calls to other APIs, you can simple pass the JWT along. There is no restriction on what domain is serving the API, so CORS, Cross-Origin Resource Sharing, is not an issue.