5 Keys to JSON Web Tokens and ASP.NET Core 2

I know JSON Web Tokens (JWT) have been around for a while, but I still run into many developers who do not know much or anything about them. You can read much more in the JWT Handbook and at one of my favorite online tool sites for using JWT at jwt.io.

Here are the 5 things I think you should know about JWT:

#1. OpenID

If you're not already using OpenID, a standard for single sign-on and identity provision, you should be looking into it. There are many sites where you can learn more about OpenID. The connect2id.com OpenID overview is a good place to start. And if you're using ASP.NET Core 2 and IdentityServer4, you definitely want to know more about JWT and OpenID.

#2. Security Concerns

You can encrypt your JWT tokens but most do not. You can include whatever you want in terms of claims in the token. Here's an example of a token trimmed down for length to simply demonstrate what an encoded token may look like. Note the "." dividers.

eyJ0eXAiOF1RSJ9.eyJpc3MiOiJNlcyIsImV4cb3JkIl19.AsYr7vaxFqCztZ_wph_

Here is what that token may look like when decoded from the Base 64 string:

Header

{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "a3rMUgMFv9tPclLa6yF3zAkfquE",
  "kid": "a3rMUgMFv9tPclLa6yF3zAkfquE"
}

Payload Data

{
  "iss": "https://identity.yourdomain.com/identity",
  "aud": "https://identity.yourdomain.com/identity/resources",
  "exp": 1512842905,
  "nbf": 1512839305,
  "client_id": "apiclient",
  "scope": "openid",
  "sub": "108434458",
  "auth_time": 1512839305,
  "idp": "youridpid",
  "amr": [
    "password"
  ]
}

So what do you need to be worried about with respect to security? Simply this. Make sure whatever you put into your JWT tokens do not reveal secrets or sensitive data that you do not want discovered. Yes, of course, the JWT is signed. The third part of the token is a hash created by the identity server and must be validated on the server side accepting the token when you use it in the Authorization header as a Bearer token.

This means the server side has to have the key used to validate. But on the client side, for unencrypted JWT tokens, the data can be useful. Just don't make it useful to the bad actor. And when you decide to add claims to your JWT from your identity server implementation, be sure you are comfortable with that data being out in the wild.

#3 Authorization Most Common Use

The most common use of a JWT is as a bearer token in the Authorization header as I've just mentioned.

Authorization: Bearer [token]

Here's the flow.

  1. Browser (or API client) sends POST to identity endpoint with username and password.
  2. Identity server authenticates and creates JWT signed with secret key or certificate.
  3. Browser (or API client) receives the JWT token.
  4. Browser (or API client) makes request to the app server with Authorization header with "Bearer [token]" in request.
  5. App server validates the JWT token and authorizes and executes the request or denies it.
  6. Browser (or API client) receives response from the application server.

#4 Getting the "sub" Claim Back from Microsoft's Mapping

Before you spend a lot of time in ASP.NET Core 2 trying to get access to the "sub" (subject) claim in the controller's User (cast as ClaimsPrincipal), just know that it's not your fault. It's Microsoft's. They map claims and are not 100% compliant with the OpenID standard in that the "sub" claim disappears. Here's how you get it back:

public void ConfigureServices(IServiceCollection services)
{
  //config services like logging, Mvc, etc.
  
  JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); 
  
  //config services.AddAuthentication and AddAuthorization
}

By removing the "sub" claim from their claim type map, it will reappear in your ClaimsPrincipal like this dummy code written simply to show how to grab the "sub" claim now that it's back.

[HttpGet("{id}"), Authorize("openid")]
public string Get(int id)
{
  var user = User as ClaimsPrincipal;
  var memId = user.Claims
      .Where(x => x.Type == "sub")
      .Select(x => x.Value).FirstOrDefault();
  return memId;
}

Try that without removing it from the type map and you'll get a big fat null.

#5 JWT Tokens Can and Should Expire

If you are using JWT and OpenID, you should make sure that your identity server sets an expiration on the token. And if your authentication request provides your with a refresh token, you should learn how in the client to refresh (call the identity server with the refresh token) your access JWT token. See auth0's details on using refresh tokens.

A refresh token is especially useful for keeping a user logged in without asking her or him to enter a username and password again. And it is a great way to avoid storing credentials locally. Yeah, don't do that.

Conclusion

If you're not using JWT or have not yet become comfortable with tokens, you owe it to yourself to do a little reading, even if you're not a coder. But if you're a coder, you definitely need to grok JWT.