Identity Verification with JSON Web Tokens

Module 4 | General Web » Security

Lesson Goals

Students should be able to:

  • articulate what a JSON Web Token is and how it's used
  • identify similarities and differences between JWTs and other authentication methods
  • protect a server-side endpoint with a JWT

Intro to JWTs

We previously talked about client-side security at a high-level. Now we're going to dive a little deeper and talk about one of the strategies for making our applications slightly more secure through the use of JSON Web Tokens.

A Case Study

One of the vulnerable scenarios we talked about when we discussed security was the following:

"someone discovers a server-side endpoint used for sharing private photographs"

This is the exact type of vulnerability we can protect ourselves against with JSON Web Tokens. There are many other types of security threats, and JWTs are not going to solve all of them, but they are a single, simple step we can take towards ensuring our applications are more secure.

What is a JWT?

Here is what a standard JWT looks like:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

At first glance, it appears to be a giant string of gobbly-gook. Does this look familiar at all? When have you seen something similar?

Discuss

With a partner, discuss the following questions:

  • What did you use API keys for? Why did you need them?
  • How did you retrieve an API key?
  • How did you use them? Where did you put them in your codebase?
  • What problems, if any, did you run into when using them?

Not only do JWTs look like API keys, they also behave in a very similar manner, with just slight differences. As a definition, both JWTs and API keys can be described as:

  • a string
  • ...that is base64url-encoded
  • ...and verifies the identity of a particular user

JWTs vs. API Keys

Let's dig into that definition a little further as we talk about the similarities and differences between JWTs and API keys.

Both are Base64Url-Encoded Strings

Encoding is the process of converting data into a format that allows for human readability and efficient transmission/storage. Base64 encoding is a convenient way to manage data in a simple, readable text format rather than as binary data. Base64Url encoding is similar to Base64, but it avoids using common reserved URL characters (e.g. / and -)

Both are passed through a request to a server

Because these strings are Base64Url-Encoded, JWTs and API Keys can be passed through with a request to a server three different ways:

  • a query parameter
  • authorization header
  • request body

Both provide an authorization 'handshake' between client and server

When a client requests a JWT, they generally provide some sort of identifying information about themselves (e.g. a username, app ID, email address, admin role, etc.) The server generates this token based on that information and signs it using a secret key. The server then sends that newly generated token back to the client so it can be used in future requests.

When the client sends this token back to the server when making a new request, the server first decodes the token, verifies all the information is correct, then gives permission for the client to proceed with their request.

So how do they differ?

The main difference between API Keys and JWTs is the type of data they contain when they are decoded. JWTs can store more custom information and metadata about the identity of a user than an API key. This allows for more granular control over permissions and access to your API.

API Keys are still useful if you simply want to provide general quick-start access to your API. (Generally an API Key gives you all or nothing: if you have a key, you have access to all the endpoints in that API.) They're also useful for keeping track of analytics data like the amount of requests made per user. (Many free, open-source APIs will limit the amount of requests you can make at set intervals.)


Anatomy of a JWT

We mentioned JWTs differ from API keys in that they can store more custom information and metadata about the identity of a user. Let's head over to JWT.io and take a look at the debugger, which will allow us to get a closer look at the guts of a JWT.

When a JWT is decoded, we can see that it's composed of three distinct parts: a header, a payload (also known as claims), and a signature. Each of these parts are encoded JSON objects that represent different types of information.

Header

The header tells us the type of token we have (in our case, a JWT), and the hashing algorithm used to encrypt the final JWT string. Think about a master lock and how it's opened. The master lock would be our "type" of lock, and the pattern to open it (3 turns to the right, 1 turn to the left, 1 turn to the right) is our "algorithm".

Payload

The second part of the JWT is made up of claims. JWT claims are represented again as a JSON Object that contains security information about the message you’re transmitting. The properties on this object ensure the authenticity of the claim, and contain information such as the issuer (identifies the application making the call), issued-at time (when was this token issued?, expiration (when will this token expire/no longer be valid?), and any additional contextual information.

Signature

The signature is a super key piece of the JWT. The signature is what's used to verify the sender of the token and ensure that the message hasn't been changed anywhere along the way.


Practice

Let's try securing a single server-side endpoint with a JWT. We'll set up a simple express server and utilize the jsonwebtoken npm library for generating and verifying a token that can be used to access our protected endpoint.

Step 1: Getting Started

  • Create a new npm project and install express and jsonwebtoken
  • Copy this boilerplate as your server.
  • Create an app variable called secretKey using app.set and set the value to a random string (Note: You would not normally store an app secret in your server like this. Because this would be considered sensitive data, you would store it in an environment variable instead.)

Step 2: Create an Authentication Endpoint

  • This should be a POST request that accepts an email and an appName.
  • Within the request handler, use the jsonwebtoken library to sign a new token. The payload should contain the email address and app name.
  • The JWT should expire in 48 hours.
  • The server should respond with a JSON object containing the generated token.

Step 3: Create Express Middleware

  • Create a new function called checkAuth to be used as middleware.
  • The checkAuth function should:
    • Check for a token that was sent through with the request's header (Note: although tokens can be sent through a request's body or params, it is generally best practice to send it through the header)
    • If no token exists, respond with a 403 and an error message that says 'You must be authorized to hit this endpoint.'
    • If a token does exist, verify the token.
    • If there is an error with the token, respond with a 403 and message that says 'Invalid token'.
    • If there is not an error with the token, then verify that the appName part of the token contains a valid application (you can determine the list of approved applications), and then call the express next() helper to complete the request handler.
    • If the appName in the token is invalid, then respond with a 403 and message that says 'Invalid application'.
  • Apply your middleware function to the PATCH request so that only an admin can edit the status of a train line.

End Goal

  • Any user can request and receive a JWT
  • A user with a token that is from an approved app can edit the status of a train
  • A user with a token that is not from an approved app cannot edit the status of a train

Extension

  • If the /authenticate endpoint's payload email address ends in @turing.io, it should also contain an admin property set to true. The admin should have special permissions and be allowed to use any DELETE endpoint, which has yet to be created.

Checks for Understanding

  • What is a JWT?
  • In what scenarios is an API key useful?
  • Why might you use a JWT over an API key?
  • What are the three ways we can send a JWT to the server?
  • What are the three parts that make up the structure of a JWT?

Further Reading & Resources

Instructor Resources