Authentication is a important part of any web applications. For decades, we follow cookie-based authentication and it works well for most of tradition applications. But today application is changing. We no longer combine presentation layer and logic layer in a single application. We tend to separate it into backend and frontend applications. Backend is used to handle logic and provide APIs for the frontend. Frontend on other hand, can be be complicated. It’s a collection of many different applications, include Single Page Application (app), iOS app, Android app or any other third party applications. All them connect to backend via backend’s APIs. These changes led to a demand of new authentication schema.

JSON Web Token (JWT) is used to send digital information that can be verifed and trusted by mean of digital signature. It comprise a compact and URL-safed JSON object, which is crytographycally signed to verify its authenticity, and which can also be encrypted if payload contains sensitive information.

This token is portable, the same token can be used many different devices. The token also contains some information of the user. JWT leaves it up to the frontend to store and handle the entire session/user object. By using JWT, our backend is more lightweight and focus on APIs only.

In this tutorial, I am going to demonstrate how to implement a basic authentication using JWT in two popular web technologies: ExpressJS for the backend and AngularJS for the Single Page Application. ( You can find the entire code at here)

ExpressJS backend

We will use ExpressJS to handle signin request, generate the token and validate token in /check request. The sigin request is very simple, we validate the data in request against hard-code username and password which are defined in the backend.

Setup project

In this project, we are going to use ExpressJS as our backend web framework. To handle authentication we use PassportJS. Jsonwebtoken library is used to generate and verify token send from client. Because our SPA is separated with the backend. We also use “cors” to enable cross-origin request. Lastly, “body-parser” is used to parse body content of request from SPA.

We can install all of them by NPM:

npm init -y
npm install body-parser@^1.14.1 —save
npm install cors@^2.7.1 —save
npm install express@^4.13.3 —save
npm install jsonwebtoken@^5.4.1 —save
npm install passport@^0.3.2 —save
npm install passport-local^1.0.0 —save

Now we can create our backend starting point.

Content of index.js

var express = require('express');
var passport = require('passport');
var bodyParser = require('body-parser');
var cors = require('cors');
var router = express.Router();
var app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.use(passport.initialize());
app.use(router);
app.use(cors());
require('./passport.js')(passport);
require('./routes/home.js')(app, passport);
var port = process.env.PORT || 3000;
app.listen(port);

This file is pretty forward, we setup our backend application, body-parse and cors. Router is setup at /routes/home.js and we configure Passport authentication stragegy at passport.js.

Content of passport.js:

var LocalStrategy = require('passport-local');
module.exports = function(passport) {
  var user = {
    id: 1,
    username: 'namtran',
    email: 'nam.trankhanh.vn@gmail.com'
  };
  passport.use('signin', new LocalStrategy({passReqToCallback: true},          function(req, username, password, done) {    
    if (username === 'namtran' && password === '123') {
      return done(null, user);
    }
    return done(new Error('Invalid username or password'));
  }));
}

We use a LocalStrategy to process authentication. The username and password in request payload need to match with our hard code username and password.

We want to guard some of our routes from unrestricted access, in order to do it we create authentication middleware. This middle will verify the JSON web token in request’s header if it unable to verify token or token is not exist, the middleware response 400 status code.

Content of authentication.js:

var jwt = require('jsonwebtoken');
function authentication(req, res, next) {
  if (!req.headers['authorization']) {
    res.status(400).send();
  }
  var token = req.headers['authorization'];
  return jwt.verify(token, 'MYSECRET', function(err, result) {
    if (err) {
      return res.status(400).send();
    }
    return next();
  });
}
module.exports = authentication;

 

The router of our app is setup at /routes/home.js. When our app receives a POST request to /sigin with username and password. We verify it and return a token via JSON request.  Once user has been signed in, he can fetch a restricted resource. So we make /restricted endpoint that simulates a restricted resource that needs an authenticated user. In order to do this, the request header need to provide token for the the backend to verify.

We also add handler for OPTIONS request to deal with pre-flight behavior.

Content of home.js:

var express = require('express');
var router = express.Router();
var authentication = require('../middlewares/authentication.js');
var jwt = require('jsonwebtoken');
var cors = require('cors');
module.exports = function(app, passport) {
	app.use('/', router);
  router.options('/sigin', cors());
  router.post('/signin', function(req, res, next) {
    return passport.authenticate('signin', function(err, user, info) {
      if (!user) {
        return res.status(400).send();
      }
      var token = jwt.sign({id: Page on Page on Page on user.id}, 'MYSECRET', {
        expiresIn: '24h'
      });
      return res.status(200).send({
        token: token
      });
    })(req, res, next);
  });
  router.options('/restricted', cors());
  router.post('/restricted', authentication, function(req, res) {
    return res.status(200).send();
  });
}

AngularJS frontend

Our frontend is make with AngularJS and it uses API from our ExpressJS backend to handle login request and simulate accessing restricted resource.

In order to get started, we use bower to install the follow dependencies:

bower install angularjs@~1.4.8
bower install bootstrap@~3.3.6

Now we create the starting point for our frontend app

Content of index.html

<html ng-app="app">
  <head>
    <title>JWT Authentication</title>
    <link rel="stylesheet" href="/public/components/bootstrap/dist/css/bootstrap.css">
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-md-6">
          <h1>JWT Authentication</h1>
          <div ng-controller="appCtrl as vm">
            <div ng-show="false == vm.isAuthenticated">
              <form class="form" ng-submit="vm.submit()">
                <div class="form-group">
                  <label>Username</label>
                  <input type="text" class="form-control" name="username" ng-model="vm.username" />
                </div>
                <div class="form-group">
                  <label>password</label>
                  <input type="password" class="form-control" name="password" ng-model="vm.password" />
                </div>
                <input type="submit" class="btn btn-primary" value="Login"/>
              </form>
            </div>
            <a href="#" ng-click="vm.fetch()">Fetch resources</a>
          </div>
        </div>
      </div>
    </div>
    <script src="/public/components/angular/angular.js"></script>
    <script src="/app/app.js"></script>
    <script src="/app/appCtrl.controller.js"></script>
    <script src="/app/tokenInjector.factory.js"></script>
  </body>
</html>

In our app, we provide a form to input username and password. After login successful this form will be hidden. When click on “Fetch resource” button, our SPA will make a POST request to /restricted endpoint. In order to make this request, user must be authenticated.

Now we create the app itself.

Content of app.js

(function() {
  'use strict';
  angular
    .module('app', [])
    .config(function($httpProvider) {
      $httpProvider.interceptors.push('tokenInjector');
    });
})();

We just created the app with no dependencies at all. In the config function, we push tokenInjector to the http interceptors. We will walkthrough token injector later.

And the appController to handle application logic.

Content of appController.js

(function() {
  'use strict';
  angular
    .module('app')
    .controller('appCtrl', appCtrl);
  function appCtrl($http) {
    var _this = this;
    this.isAuthenticated = false;
    this.submit = function() {
      return $http
        .post('Page on localhost:3000', {
          username: _this.username,
          password: _this.password
        })
        .then(function(response) {
          _this.isAuthenticated = true;
          localStorage['token'] = response.data.token;
          alert('Login successful');
        }, function() {
          _this.isAuthenticated = false	;
          alert('Login fail');
        });
    }
    this.fetch = function() {
      return $http
        .post('Page on localhost:3000')
        .then(function() {
          alert('Fetch resource successful');
        }, function() {
          alert('Can not fetch resource');
        });
    }
  }
})();

Our form is bind with submit function, when this function is called. It will make a POST request to /sigin with username and password. If the username and password is correct we take the token from response and store it in localStorage. After that the form is hidden.

The “Fetch resource” button is bind with fetchResource() function, when click it will make a request to /restricted endpoint.

tokenInjector

Without tokenInjector, when we need to inject our token to our request we need to do like this.

this.fetch = function() {
  var token = localStorage['token'];
  return $http
    .post('Page on localhost:3000', {}, {
      headers: {
        Authorization: token
      }
    })
    .then(function() {
      alert('Fetch resource successful');
    }, function() {
      alert('Can not fetch resource');
    });
}

We don’t want to repeat this code in hundred of requests so we can use a httpInterceptor to automattically do it for us. That’s why we make tokenInjector. Our tokenInjector will look for token in localStorage, if token is exists in localStorage it will take the token and inject to header to http request. Our backend will use this token to authorize our request.

Conclusion

Token-based authentication enables us to construct decoupled system that are not tied to any particular authentication schema. This token might be generated anywhere and consumed on any system that use same secret for sigining the token. They are mobile-ready and do not require us to use cookies.

There are still a lot to cover about JWT, such as how to handle security details and refresh tokens when they expire but the tutorial above should demonstrate the basic usage and more important, advantages of using JSON web token.