Secure a Koa API With a JWT Token

November 21, 2016Bruno Godefroy6 min read

thumbnail

Koa is the “new” all the rage framework used with NodeJS. This guide aims at explaining how to set up efficiently an API protected with a JWT token.

What we want to have at the end of this tutorial is an API protected from unauthenticated users.

We will use a JWT token to authenticate our users.

What is JWT? Why should we use it?

JWT stands for JSON web token. It is a standard token designed to exchange secured information and is mainly used for authentication purposes. JSON web tokens are small sized which allows them to be sent:

  • through a URL
  • a POST parameter
  • inside a HTTP header.

They are composed of three different parts separated by a dot:

  • Header: contains typically the hashing algorithm used and the type of the token which is JWT.
  • Payload: the information you want to exchange with the token. Could be the user id and its role for example in the case of an authentication token.
  • Signature: is used to verify the authenticity of the token. The signature is generated using the header, the payload and a secret. It can thus be used to check that the payload has not been altered.

A classic JWT token looks like the following: <header>.<payload>.<signature>

In an authentication scenario, JWT tokens are used in the following way:

  • the user sends a login request with his credentials
  • the server authenticate the user
  • the server creates a token containing some information to recognise the user in the future
  • the server signs the token
  • the server sends the token back to the user
  • the user stores the token in the locale storage or in the cookies.

On his next requests:

  • the user sends the token in the header so that the server can identify him.
  • the server decodes the token to read the payload which can be used to identify the user
  • the server treats his request.

The user can’t change the payload of his token to usurp the identity of another user. The payload is indeed used to generate the signature and an altered token will not be recognised as valid by the server.

JWT tokens for authentication have the main advantage to not require session. The token is like an access card and it is the user who stores it, usually in a cookie or locale storage. The backend does not keep any tracks of the issued tokens.

The secret is not shared with the user, it is only stored on the server which makes it less likely to be cracked.

JSON web tokens are also way smaller than SAML tokens which are their XML equivalent. It makes them a better choice to be passed in HTML and HTTP environments.

One drawback of using JWT is that a malicious user can make requests on someone else’s name if he managed to find a valid token. That is the reason why it is important to use SSL to protect the token that will be sent on every request.

Set up: our unprotected API

All the code is available at https://github.com/Theodo-UK/blog-koa-jwt

In this guide we will use an API with one entity and two methods available (GET and POST). Another endpoint will be created later on to allow users to log in. Here is a code sample for an unprotected API using koa, koa-router and koa-better-body to facilitate the parsing of the request. Note that the requests are expected to have a content type application/json.

//index.js

const app = require('koa')();
const router = require('koa-router')();
const koaBetterBody = require('koa-better-body');

const customerService = require('./services/customerService');

app.use(koaBetterBody({fields: 'body'}));

router.get('/customer', function *(next) {
  this.body = customerService.getCustomers();
});

router.get('/customer/:id', function *(next) {
  if (customerService.getCustomer(this.params.id)) {
    this.body = customerService.getCustomer(this.params.id);
  }
  else {
    this.status = 404;
    this.body = {"error": "There is no customer with that id"};
  }
});

router.post('/customer', function *(next) {
  this.body = customerService.postCustomer(this.request.body);
});

app
  .use(router.routes())
  .use(router.allowedMethods());

app.listen(3000);

Here the customerService is supposed to make the link between the API and the database. For the purpose of this example, this service is simply managing a javascript object containing the list of our customers as you can see below:

//services/customerService.js

const customers = [
  {
    "id": 1,
    "first_name": "John",
    "last_name": "Smith",
    "date_of_birth": "1993-04-23T00:00:00.000Z"
  },
  {
    "id": 2,
    "first_name": "Justin",
    "last_name": "Bieber",
    "date_of_birth": "1994-04-23T00:00:00.000Z"
  }
];

let maxId = 2;

module.exports = {
  getCustomers: function() {
    return customers;
  },
  getCustomer: function(id) {
    return customers.find(customer => customer.id === parseInt(id) || customer.id === id);
  },
  postCustomer: function(customer) {
    maxId++;
    customer.id = maxId;
    customers.push(customer);
    return this.getCustomer(maxId);
  }
}

With this code you should be able to call your API to get the list of customers and post new ones. For the moment, anyone can call the API but you probably want to restrict your API to authenticated users only. In the following section, we will see how to protect the POST endpoint from unauthenticated users.

1st step: protect your API

We want to protect the API using JWT tokens, and for that we will use a middleware called koa-jwt. You can add this new file to your app to set up the middleware with your secret key:

//middlewares/jwt.js

const koaJwt = require('koa-jwt');

module.exports = koaJwt({
  secret: 'A very secret key', // Should not be hardcoded
});

The secret key should not be hardcoded but instead you can use an environment variable. Note that it is possible to use different secret key depending on the endpoint you are protecting for example. It is even possible to have different secret keys for each of your users.

You can now import the middleware in you main file:

//index.js

const jwt = require('./middlewares/jwt');

It is then really easy to protect a specific endpoint, you just have to add the middleware to the stack of middlewares called by the endpoint, for example for our POST endpoint becomes:

//index.js

router.post('/customer', jwt, function *(next) {
  this.body = customerService.postCustomer(this.request.body);
});

If you now try to call this protected endpoint you should receive an error 401 with the message: “No Authorization header found”. What is actually happening is that the jwt middleware checks if the request has the required authentication header, and if not, stops the stack of middleware and returns a 401.

You could try to add the jwt middleware earlier in the stack of middleware, then you would not be able to access any of the middlewares called afterwards. For example, if you add app.use(jwt); before declaring the first endpoint, none of the endpoints are accessible.

Now that one of your endpoint is protected, it would be nice to be able to allow user authentication.

2nd step: create a login endpoint and mechanism in the API

First we need to create a new endpoint that has to be unprotected. Let’s add the following code before the first call to jwt:

//index.js

router.post('/login', function *(next) {
  authenticate(this);
});

And import the new authenticate middleware const authenticate = require('./middlewares/authenticate.js');

The authenticate middleware is the one in charge of the login mechanism. This is where you will check the credentials and also where you will create the jwt token if the credentials are valid.

//middlewares/authenticate.js

const jwt = require('koa-jwt');

module.exports = function (ctx) {
  if (ctx.request.body.password === 'password') {
    ctx.status = 200;
    ctx.body = {
      token: jwt.sign({ role: 'admin' }, 'A very secret key'), //Should be the same secret key as the one used is ./jwt.js
      message: "Successfully logged in!"
    };
  } else {
    ctx.status = ctx.status = 401;
    ctx.body = {
      message: "Authentication failed"
    };
  }
  return ctx;
}

This middleware is quite simple, it:

  • checks that the password sent with the request is the right one
  • in that case returns the jwt token in the body of the answer.

In that example the token contains only one attribute role set as admin for anyone. The token is then signed using the same secret key than the one used to decode the token defined in middlewares/jwt.js. In the case of bad credentials, the middleware simply return a 401 with a message.

You can now access the protected endpoints by adding the jwt token in the request Authorization header: Bearer <token>.

Conclusion

You now have a ready to go protected api with a simple authentication mechanism! Enjoy!

Bruno Godefroy

Bruno Godefroy

Web Developer at Theodo