Express, Passport and JSON Web Token (jwt) Authentication for Beginners

This post is going to be about creating an authentication with JSON Web Tokens for your project, presumably an API that's going to be used by Angular, Vue.js or similar frontend frameworks. We're going to send the jwt with every request, meaning that we don't rely on sessions, but simply put the token on every request we make to the API. This way you don't have to worry about cookies, but you can save it in localStorage or other places on the frontend.

In essence this tutorial will go through:

  • creating a /login route to acquire a token
  • creating a /secret route, that only is available to logged in users with a JSON web token

If you're curious about the final result and don't want the step by step guide, check out the final jwt express gist.

Updated 10th of August 2017, thanks to the feedback from micaksica!

What you'll need:

  • Postman or a similar tool to test the requests
  • Node, npm and a text editor

First of all we need to set up our express project, so we go ahead and create a directory and an index.js file.

Next, we're going to install the dependencies for an express based API and the passport.js strategies for JSON web tokens.

npm init -y
npm install --save express body-parser passport passport-jwt jsonwebtoken lodash

First, let's create a simple page with express with the index.js file:

// file: index.js

var express = require("express");
var app = express();


app.get("/", function(req, res) {
  res.json({message: "Express is up!"});
});

app.listen(3000, function() {
  console.log("Express running");
});

You can node start the express server with node index.js, but for development I recommend nodemon, installed by npm -g install nodemon to make sure that your server restarts when ever you change the file.

The result you should see in your browser now should be similar to the image below:

expressjs-json-response

Example Authentication with passport.js and JSON web tokens

In order to get the next iteration of our code working, we'll need to add a couple of more packages to our index.js file:

// file: index.js
var _ = require("lodash");
var express = require("express");
var bodyParser = require("body-parser");
var jwt = require('jsonwebtoken');

var passport = require("passport");
var passportJWT = require("passport-jwt");

var ExtractJwt = passportJWT.ExtractJwt;
var JwtStrategy = passportJWT.Strategy;

_ or lodash will just be for a small utility function, everything passport related will be used in functions for user login and token validation.

Next up is our fake users array, usually you'd get your users from the database, but we'll just use a plain JavaScript array:

var users = [
  {
    id: 1,
    name: 'jonathanmh',
    password: '%2yx4'
  },
  {
    id: 2,
    name: 'test',
    password: 'test'
  }
];

Security notice: Don't ever save passwords in plain text. Every time you do outside of a learning environment, an ancient god decapitates an alpaca!

  1. Use bcrypt (or similar strength)
  2. Read The OWASP wikis Password Storage Cheat Sheet (it's really good)

The Passport JWT strategy

passport.js works with the concept of strategies. They basically are a middleware function that a requests runs through before getting to the actual route. If your defined authentication strategy fails, which means that the callback will be called with an error that is not null or false as the second argument, the route will not be called, but a 401 Unauthorized response will be sent.

Here the strategy we're going to use for the web token authentication:

var jwtOptions = {}
jwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeader();
jwtOptions.secretOrKey = 'tasmanianDevil';

var strategy = new JwtStrategy(jwtOptions, function(jwt_payload, next) {
  console.log('payload received', jwt_payload);
  // usually this would be a database call:
  var user = users[_.findIndex(users, {id: jwt_payload.id})];
  if (user) {
    next(null, user);
  } else {
    next(null, false);
  }
});

passport.use(strategy);

Note: The API for the jwt package has changed and now should be:

jwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();

Our strategy is configured to read the JWT from the Authorization http headers of each request. Instead of ExtractJwt.fromAuthHeader() you can define an number of other extraction methods or even write your own. See the passport-jwt repository for the full list.

The secretOrKey is the secret that our tokens will be signed with. Choose this wisely or use a private key.

Inside our strategy we console.log the received payload for debugging purposes, try to find a user with a matching id field in our array and return either no error and the user object or false if the user was not found.

Express.js and the JSON Web Token login route

Let's continue to update our express app with the body parser and the login route:

var app = express();
app.use(passport.initialize());

// parse application/x-www-form-urlencoded
// for easier testing with Postman or plain HTML forms
app.use(bodyParser.urlencoded({
  extended: true
}));

// parse application/json
app.use(bodyParser.json())

app.get("/", function(req, res) {
  res.json({message: "Express is up!"});
});

This part almost stays the same, we need to initialize passport with express and enable the bodyParser (urlencoded for usual forms and json for requests with type: application/json, common for APIs).

app.post("/login", function(req, res) {
  if(req.body.name && req.body.password){
    var name = req.body.name;
    var password = req.body.password;
  }
  // usually this would be a database call:
  var user = users[_.findIndex(users, {name: name})];
  if( ! user ){
    res.status(401).json({message:"no such user found"});
  }

  if(user.password === req.body.password) {
    // from now on we'll identify the user by the id and the id is the only personalized value that goes into our token
    var payload = {id: user.id};
    var token = jwt.sign(payload, jwtOptions.secretOrKey);
    res.json({message: "ok", token: token});
  } else {
    res.status(401).json({message:"passwords did not match"});
  }
});

The login route checks if both name and password have been posted, if so, we look for the corresponding user in our fake users array.

If no user has been found, we signal that to the user in question.

Now if a user has been found and the password is correct, we set the payload for the JWT to {id: user.id} we use the jsonwebtoken package to create the token and respond with it.

Note: Put simple identifiers in your token, no sensitive data like passwords.

We can now test if our login functionality works as expected with Postman. Set the HTTP method to POST and the URL to http://localhost:3000. The type is x-www-form-urlencoded (you can also post RAW json if you want to).

Here we see a wrong password response for our login attempt:

postman-x-www-form-urlencoded

A correct username and password combination should yield the following result:

postman-post-request-login

Accessing The passport-jwt Protected API Route

So far, so good. We have created a login function that works and we get a token. Now we need to use that token to be able to access some kind of secret information.

app.get("/secret", passport.authenticate('jwt', { session: false }), function(req, res){
  res.json("Success! You can not see this without a token");
});

app.listen(3000, function() {
  console.log("Express running");
});

We see our /secret route and that we have a second function defined before we finally pass req and res on. The passport.authenticate part means that we pass the request through our previously defined authentication strategy and run it. If it's successful, we respond with the secret message, else the request will be unauthorized (401).

To test if this works, first log in and copy the token, then open a new Postman tab and change the settings to:

  • method: GET
  • URL: http://localhost:3000/secret
  • inside Headers: add Authorization and add JWT + token

Note: It's important the Auth header starts with JWT and a whitespace followed by the token, else passport-jwt will not extract it.

postman-http-authorization-headers

Express and Debugging Passport or Middleware

In the process I of course forgot to prepend JWT so I created a temporary route for debugging my request that would console.log the received headers:

app.get("/secretDebug",
  function(req, res, next){
    console.log(req.get('Authorization'));
    next();
  }, function(req, res){
    res.json("debugging");
});

Terminal output:

[nodemon] restarting due to changes...
[nodemon] starting `node index.js`
Express running
JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNDc3MTM0NzM4fQ.Ky3iKYcguIstYPDbMbIbDR5s7e_UF0PI1gal6VX5eyI

See the full files on github:

JSON Web Token Tutorial: Express

In the next post we'll have a look at how we can use the issued web token with JavaScript inside the browser and have protected API access.

If you liked this post, feel free to subscribe on feedly, follow on twitter or check out some of my other posts. If you think I could have explained or done something better or more beginner friendly, please let me know in the comments!

Further Steps

Most importantly: Don't put secret stuff into a JWT, unless you've read the following post and know what you're doing.If you want to build anything half-way secure, you should use a token store that associates a generated string with a user-id backed by redis, in memory or your database like MongoDB or SQL. These rows or documents will basically store a session equivalent with the following fields:

  • the token
  • the user id
  • expiration time

Don't put your stuff into production without this ;)

Tagged with: #express.js #JSON Web Token #node.js #passport.js passport-jwt #Postman

Thank you for reading! If you have any comments, additions or questions, please tweet or toot them at me!