Home Blog CV Projects Patterns Notes Book Colophon Search

JSON Web Tokens with Express

9 Nov, 2016

JSON Web Tokens are a way of encoding data about a user into a long string that you can put somewhere in an HTTP request to authenticate a user and get information about their permissions, just by checking a signature on the token. You can also use the token as part of a single sign on system, or as a bearer token in an OAuth2 flow.

(The core concept of the token is exactly the same idea as the ticket in mod_auth_tkt which I have been using for over 10 years and built into AuthKit for Pylons.)

The jwt.io site has a set of libraries and a basic tool for playing with tokens.

One nice thing about JSON Web Tokens (JWT from now on) is that you can choose different methods for signing them. mod_auth_tkt relied on a shared secret that both the client and server knew, JWT can do the same, but it can also use RSA public and private keys. This means one party can create a token, and another one check it, without either needing to have the key of the other.

Here's how you set up a simple npm project to sign a key:

cat << EOF > package.json
{
  "name": "keys",
  "version": "1.0.0",
  "description": "",
  "main": "src/sign.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jsonwebtoken": "^7.1.9"
  }
}
EOF
npm install
mkdir -p src
cat << EOF > src/sign.js 
var jwt = require('jsonwebtoken');
var fs = require('fs');
 
// sign with RSA SHA256 
var cert = fs.readFileSync('key.rsa');  // get private key 
jwt.sign({ foo: 'bar' }, cert, { algorithm: 'RS256' }, function(err, token) {
  console.log(token);
});
EOF

Now you can generate a public private key pair:

openssl genrsa -out key.rsa 
openssl rsa -in key.rsa -pubout > key.rsa.pub

For interest, here our the keys:

$ cat key.rsa
-----BEGIN RSA PRIVATE KEY-----
MIIBPAIBAAJBAKpcMjkDWGsL9uYX+NNOQEpQNI/WRqKTScOGY/XRH/YdSWQEKtY+
jy4ItYL1x9MYca6j+cd//mqbKYJICZgkwosCAwEAAQJBAIVbYp0qtz4kwYNg5JFU
whLqMhsem3CMZ8O9Iea9a42WHGM5iL/e5w3K/1kceWPT/VjADB9R445Qp04q/mYs
eeECIQDVZDtQlXtDetJph0nbXVXgSg3a6WMVZUeH98HOu/5Z0wIhAMxgXNFiNT57
n2G6rpNtBUJbTtQHpm6QUZjTCdMuQIlpAiBp/nx7/ZQZo6NqaZnlDYp/eylAqJbf
5MvQHN+2uaiQowIhAKvs6EfvoLDGWAZjf5ZfYMw4eXCeWuCpoSq5ZYt0Xi/5AiEA
se9WsJmrRkRksiAuOqtoQ9GvTzvPzDPHIdkD7STyWeE=
-----END RSA PRIVATE KEY-----
$ cat key.rsa.pub 
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKpcMjkDWGsL9uYX+NNOQEpQNI/WRqKT
ScOGY/XRH/YdSWQEKtY+jy4ItYL1x9MYca6j+cd//mqbKYJICZgkwosCAwEAAQ==
-----END PUBLIC KEY-----

Using the private key we'll create a JSON web token to sign { foo: 'bar' } with RS256:

node src/sign.js

Giving our JSON Web Token:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0ODA1MDE4ODh9.lTBYByWrQ10g_J2l149_Z8RCaAX4ITge1c7fvJEF3UX7lFQRlRx0KeI-eJd-7Bst_2Nn9xigFIdhYO4lvUXiPQ

We can now use an online tool to test the token. Visit http://jwt.io and scroll down a bit and you'll see the Debugger.

Paste the token in the left hand side and you'll see the decoded (but not verified) data from the token in the top right.

To verify it, paste the public key into the top box that appears, leaving the private key box empty. You'll see "Signature Verified" in big letters at the bottom. If you tamper with the token or the certificate you'll see "Invalid Signature".

JWT RSA Example ScreenshotScreenshot for jwt.io

Now we need to be able to verify signatures in an application.

If you have an express application, you can use express-jwt. It comes with typescript bindings too:

npm install --save express
npm install --save-dev @types/express
npm install --save express-jwt
npm install --save-dev @types/express-jwt

You can use it as middleware:

cat << EOF > src/server.js
var express = require('express');
var fs = require('fs');
var jwt = require('express-jwt');

var app = express();

var publicKey = fs.readFileSync('key.rsa.pub');

app.use(jwt({
  secret: publicKey,
  credentialsRequired: false,
  getToken: function fromHeaderOrQuerystring (req) {
    if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
        return req.headers.authorization.split(' ')[1];
    } else if (req.query && req.query.token) {
      return req.query.token;
    }
    return null;
  }
}));

app.get('/', function (req, res) {
  res.send('Hello World! User data: ' + JSON.stringify(req.user) + '\n');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});
EOF
node src/server.js

With the server running you can do some experiments with curl:

$ curl http://localhost:3000
Hello World! User data: undefined
$ curl http://localhost:3000?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0ODA1MDE4ODh9.lTBYByWrQ10g_J2l149_Z8RCaAX4ITge1c7fvJEF3UX7lFQRlRx0KeI-eJd-7Bst_2Nn9xigFIdhYO4lvUXiPQ
Hello World! User data: {"foo":"bar","iat":1480501888}
$ curl http://localhost:3000?token=INVALID
UnauthorizedError: jwt malformed<br> &nbsp; &nbsp;at /Users/jgardner/keystest/node_modules/express-jwt/lib/index.js:101:22<br> &nbsp; &nbsp;at /Users/jgardner/keystest/node_modules/express-jwt/node_modules/jsonwebtoken/index.js:54:18<br> &nbsp; &nbsp;at _combinedTickCallback (internal/process/next_tick.js:67:7)<br> &nbsp; &nbsp;at process._tickCallback (internal/process/next_tick.js:98:9)

Copyright James Gardner 1996-2020 All Rights Reserved. Admin.