Securely signing requests with Rewt

SHARE ON

This blog post is part of the Mixmax 2016 Advent Calendar. The previous post on December 4th was about CORs headers.

Microservice-based architectures have a lot of server-to-server communication. Some non-trivial portion of that communication will be with APIs that are considered “internal”. To secure this communication, people will often just lock these services away in private networks with strong subnet access controls. However, it’s often much nicer if you also provide some manner of authenticating requests between servers, or at least a mechanism for verifying that the request is coming from a trusted source. This approach will also let you secure services that aren’t fully internal and so can’t be put inside a private network.

Enter JSON web tokens (JWTs). In short, JWT is a standard for generating tokens that assert a claim and whose legitimacy can be easily verified. We wanted a simple way of generating JWTs for our internal communication. Furthermore we wanted to be easily able to rotate the shared secret used to sign and verify the tokens. So we wrote rewt – a simplified wrapper for signing JWTs with a shared secret sourced from Redis, that automatically rotates the secret on a predefined interval.

Initializing rewt

Initializing an instance of root is as simple as telling it where to source the shared secret from.

const redis = require('redis');
const Rewt = require('rewt');

let rewt = new Rewt({
  redisConn: redis.createClient('redis://localhost:6379')
});

By default, rewt will namespace the secret key under the rewt: namespace with a default TTL of a day. To have the key rotate on a faster interval, or you use a different namespace, you can provide alternate parameters to the constructor:

let rewt = new Rewt({
  redisConn: redis.createClient('redis://localhost:6379'),
  redisNamespace: 'foobar',
  ttl: 60 * 60 // One hour in seconds
});

Using rewt

Signing a payload with rewt is extremely simple, just provide the payload to sign and a callback to receive the signed payload:

rewt.sign({username: 'hello@hello.com'}, (err, signed) => {
  console.log(`signed payload: ${signed}`);
});

Note that the first parameter to sign doesn’t have to be an object, it can also be a buffer or a string. Verifying a payload is equally as easy:

rewt.verify(token, (err, payload) => {
  console.log(`verified payload: ${JSON.stringify(payload, null, '  ')}`);
});

Worked Example using rewt

It may not be immediately obvious of how to use rewt, so in this quick example
we will sign a payload with rewt, add it as an authorization header and then
verify that payload and extract the relevant information.

Signing an HTTP request with rewt

Signing the request simply uses the sign functionality to create the token
and then embed it as an HTTP Authorization header (as a Bearer token).

function retrieveProtectedResource(userId, resource, done) {
  rewt.sign({ userId }, (err, signed) => {
    if (err) return done(err);

    request({
      uri: resource,
      method: 'GET',
      auth: {
        bearer: signed
      }
    }).on('error', (err) => {
      done(err);
    }).on('response', (response) => {
      done(null, response);
    });
    console.log(`signed payload: ${signed}`);
  });
}

Verifying a request using rewt

We can easily build an express middleware component that extracts the userId
from the signed payload above and places it on the incoming request object.

const BEARER = 'Bearer';

function rewtMiddleware(req, res, next) {
  if (!req.headers.authorization || req.headers.authorization.indexOf(BEARER) !== 0) {
    return next();
  }

  let token = req.headers.authorization.slice(BEARER.length).trim();
  rewt.verify(token, (err, payload) => {
    if (err) return next(err);

    if (payload && payload.userId) {
      req.user = { _id: payload.userId };
    }
    next();
  });
}

Voila! We now have a mechanism for ensuring that an incoming request was sent
from a trusted source.

Notes

One thing to note is that since the key is in Redis and rewt handles rotating it for you, if you ever need to rotate the key manually you can simply remove the key from Redis yourself and rewt will generate a new one to use on the next query.

Careful readers will also have noted that we spoke about the key having a TTL. This does mean that there is the miniscule chance that the key may be rotated while a request is in flight. We do not consider this as a large drawback since one should already be using appropriate retry policies for specific error classes and because this time window is very small.

Enjoy infrastructure security? Drop us a line.

SHARE ON

Written By

Trey Tacon

Trey Tacon

From Your Friends At