en
de

Secure your modern Web App with JWT

Your new and shiny web app probably runs in the cloud and will scale appropriately to the number of requests hitting it. How do I ensure that a client request is properly authenticated – in particular given that any server instance can handle the client request? This is where JWT comes in to save the day.

What is it?

First of all, we should have a look at what a JWT (JSON Web Token) is. The JWT between client and server is a string composed out of three parts: Header, Payload, and Signature. The first two parts contain meta data and content, whilst the third part is a digital signature created by a special algorithm for guaranteeing the authenticity of the first two parts. Such a three-part string might look like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxNDg5MDY1NDAwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZXMiOlsiYWRtaW4iXX0._4RCbbM50WyGDLIAnsjIyOByFZdjyV9wPgWsSXAsyd0

Let’s have a look at the contents of those three sections.

Header

The Header section contains the algorithm used to calculate the Signature as well as the type of token dealing with.

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

Payload

In the Payload object, you can put any data you want to exchange. Just be aware that this token will be transmitted between server and client on each request. So, you shouldn’t store your whole state in here. Furthermore, for authentication security purposes, an expiry date should be added so tokens will expire at some given point.

{
  “exp”: “1489065400”,
  “name”: “John Doe”,
  “roles”: [
    “admin”
  ]
}

Signature

The last section is the Signature. It’s the only one not base64-encoded. In here, the header and the payload objects get digitally signed with the algorithm set in the Header section. Everyone holding the signing secret is able to generate new valid tokens. So, make sure not to publish this to your client.

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

How do we use a JWT?

Let’s look at the usage of such tokens in a more real world example. We’re going to create a small frontend (React) that will talk to a server (NodeJs/Express) to a public API and a secure one where we have to provide a valid JWT. We could create and sign our own tokens for this, but for the sake of simplicity, we’ll use Auth0 as our authentication and JWT provider. They will provide us with a login screen that can use OAuth with common Social Providers like Twitter and Facebook as well as a standard username/password method. To see which frameworks and platforms they provide example code for you can visit their documentation: https://auth0.com/docs.

You can find the code for this Exercise on Github. To be able to run your own login screen you need to create a free account on https://auth0.com.

1. Create an Auth0 Account

After creating this account, you will be guided to create your first client named Default App. What we will need in our application are the Domain, ClientId and the Client Secret.

Create Autho Account

Also make sure, that you set the Origin URLs below those credentials to http://localhost:3000. Otherwise our call to Auth0 will get blocked because of CORS restrictions.

2. Create Frontend Authentication Service

Auth0 will provide you with a default implementation for the authentication service. Let’s look at the default implementation enhanced with expiration checks.

import Auth0Lock from 'auth0-lock';
import jwtDecode from 'jwt-decode';

const tokenIsExpired = (token) => {
  try {
    // we do not check the signature here because we do not want our SECRET on the client side
    const decodedToken = jwtDecode(token);
    return new Date(0).setUTCSeconds(decodedToken.exp) < new Date();
  } catch (e) {
    console.error(e);
    return true;
  }
};

const Prototype = {
  login() {
    // show auth0 popup
    this.lock.show();
  },
  loggedIn() {
    const token = this.getToken();

    if (token) {
      if (!tokenIsExpired(token)) {
        return true;
      }

      this.logout();
    }

    return false;
  },
  logout() {
    localStorage.removeItem('id_token');
  },
  setToken(idToken) {
    // save token in local storage
    localStorage.setItem('id_token', idToken)
  },
  getToken() {
    return localStorage.getItem('id_token');
  },
};

export default {
  create(onLoggedIn) {
    const obj = Object.create(Prototype);

    // replace clientId and domain with you auth0 params
    obj.lock = new Auth0Lock('clientId', 'domain', {
      auth: {
        redirectUrl: 'http://localhost:3000',
        responseType: 'token',
        params: {
          // with this param we can control what will get included in the payload of the returned JWT
          scope: 'openid email app_metadata',
        },
      },
    });

    // Add callback for lock `authenticated` event
    obj.lock.on('authenticated', (authResult) => {
      obj.setToken(authResult.idToken);
      onLoggedIn(obj);
    });

    return obj;
  },
};

You can see that we check the expiry date of this token but not the signature. It could be a faked token since we do only decode the middle part of the token with base64. But, it’s enough to only validate the signature on our server side.

In contrast to using cookies, our browser won’t automatically append the token to each server request. We have to manually add this token. Depending on whether you use fetch or XHR, this will look a bit different. Here, you can see the implementation using fetch.

fetch('/secretApi', {
  headers: {
    'Authorization': 'bearer ' + token
  }
})

3. Parse JWT on Server Side

This is already the entire code we need for a basic authentication mechanism on our client side. To check a received JWT on the server side we use a small express server.

const express = require('express');
const expressJWT = require('express-jwt');

const app = express();

const server = app.listen(3000, () => {
  console.info(`Listening on port ${server.address().port}`);
});

app.use('/', express.static('./'));

// replace Secret and ClientId with you auth0 params
const authorization = expressJWT({
  secret: 'Secret',
  audience: 'ClientId',
});

app.get('/secretApi', authorization, (req, res) => {
  res.sendStatus(200);
});

We use the express-jwt middleware to help us parse and decode the JWT. All we have to do now is to provide our Secret and ClientId to set it up. This middleware can now easily be added to every endpoint that should be authenticated. It will fail and return 401 Unauthorized to a client without a valid token.

That’s it We just created a very basic app that authenticates with JWT. To run the app and click through the frontend to see these tokens in action go head over to Github and check out the code. There are more options available to customize the payload of your token. You might want to check out the Auth0 docs to get an idea what else you can do with the payload of your tokens like putting in user roles.

Conclusion

As you can see, we are able to store small amounts of our state in such a token that can be created by a completely independent service. In our example Auth0. Now, we can exchange this state between client and server and simultaneously validate the token on any instance of our server without the need of a server side session.

A word of caution, though: since you’re transmitting this token between server and client with every request and response you should not include large amounts of data in your token. So, better handle the users’ avatar image somewhere else.

Tell me if this helped you or if you have further questions. If you find errors in the app, best open issues directly on Github.

Comments (0)

×

Sign up for our Updates

Sign up now for our biweekly updates.

This field is required
This field is required
This field is required

I'm interested in:

Select at least one category
You were signed up successfully.