Tuesday 4 November 2014

JWT usage example

Good, easy to understand JSON web token (JWT) usage example, which does not contain much unrelated stuff:
https://github.com/auth0/angular-token-auth

Article with explanation:
https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/

Listings just in case if it suddenly disappear:

auth.server.js
var express = require('express');
var bodyParser = require('body-parser');

var jwt = require('jsonwebtoken');  //https://npmjs.org/package/node-jsonwebtoken
var expressJwt = require('express-jwt'); //https://npmjs.org/package/express-jwt


var secret = 'this is the secret secret secret 12356';

var app = express();

// We are going to protect /api routes with JWT
app.use('/api', expressJwt({secret: secret}));

app.use(bodyParser.json());
app.use('/', express.static(__dirname + '/'));

app.use(function(err, req, res, next){
  if (err.constructor.name === 'UnauthorizedError') {
    res.status(401).send('Unauthorized');
  }
});

app.post('/authenticate', function (req, res) {
  //TODO validate req.body.username and req.body.password
  //if is invalid, return 401
  if (!(req.body.username === 'john.doe' && req.body.password === 'foobar')) {
    res.status(401).send('Wrong user or password');
    return;
  }

  var profile = {
    first_name: 'John',
    last_name: 'Doe',
    email: 'john@doe.com',
    id: 123
  };

  // We are sending the profile inside the token
  var token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 });

  res.json({ token: token });
});

app.get('/api/restricted', function (req, res) {
  console.log('user ' + req.user.email + ' is calling /api/restricted');
  res.json({
    name: 'foo'
  });
});

app.listen(8080, function () {
  console.log('listening on http://localhost:8080');
});


auth.client.js
var myApp = angular.module('myApp', []);

//this is used to parse the profile
function url_base64_decode(str) {
  var output = str.replace('-', '+').replace('_', '/');
  switch (output.length % 4) {
    case 0:
      break;
    case 2:
      output += '==';
      break;
    case 3:
      output += '=';
      break;
    default:
      throw 'Illegal base64url string!';
  }
  return window.atob(output); //polifyll https://github.com/davidchambers/Base64.js
}

myApp.controller('UserCtrl', function ($scope, $http, $window) {
  $scope.user = {username: 'john.doe', password: 'foobar'};
  $scope.isAuthenticated = false;
  $scope.welcome = '';
  $scope.message = '';

  $scope.submit = function () {
    $http
      .post('/authenticate', $scope.user)
      .success(function (data, status, headers, config) {
        $window.sessionStorage.token = data.token;
        $scope.isAuthenticated = true;
        var encodedProfile = data.token.split('.')[1];
        var profile = JSON.parse(url_base64_decode(encodedProfile));
        $scope.welcome = 'Welcome ' + profile.first_name + ' ' + profile.last_name;
      })
      .error(function (data, status, headers, config) {
        // Erase the token if the user fails to log in
        delete $window.sessionStorage.token;
        $scope.isAuthenticated = false;

        // Handle login errors here
        $scope.error = 'Error: Invalid user or password';
        $scope.welcome = '';
      });
  };

  $scope.logout = function () {
    $scope.welcome = '';
    $scope.message = '';
    $scope.isAuthenticated = false;
    delete $window.sessionStorage.token;
  };

  $scope.callRestricted = function () {
    $http({url: '/api/restricted', method: 'GET'})
    .success(function (data, status, headers, config) {
      $scope.message = $scope.message + ' ' + data.name; // Should log 'foo'
    })
    .error(function (data, status, headers, config) {
      alert(data);
    });
  };

});

myApp.factory('authInterceptor', function ($rootScope, $q, $window) {
  return {
    request: function (config) {
      config.headers = config.headers || {};
      if ($window.sessionStorage.token) {
        config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
      }
      return config;
    },
    responseError: function (rejection) {
      if (rejection.status === 401) {
        // handle the case where the user is not authenticated
      }
      return $q.reject(rejection);
    }
  };
});

myApp.config(function ($httpProvider) {
  $httpProvider.interceptors.push('authInterceptor');
});


index.html
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Angular Authentication</title>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js"></script>
    <script src="./auth.client.js"></script>
  </head>
  <body ng-app="myApp">
    <div ng-controller="UserCtrl">
      <span ng-show="isAuthenticated">{{welcome}}</span>
      <form ng-show="!isAuthenticated" ng-submit="submit()">
        <input ng-model="user.username" type="text" name="user" placeholder="Username" />
        <input ng-model="user.password" type="password" name="pass" placeholder="Password" />
        <input type="submit" value="Login" />
      </form>
      <div>{{error}}</div>
      <div ng-show="isAuthenticated">
        <a ng-click="callRestricted()" href="">Shh, this is private!</a>
        <br>
        <div>{{message}}</div>
        <a ng-click="logout()" href="">Logout</a>
      </div>
    </div>
  </body>
</html>


package.json
{
  "name": "angular-token-auth",
  "version": "0.1.0",
  "dependencies": {
    "body-parser": "^1.9.0",
    "express": "~4.9.0",
    "express-jwt": "~0.2.1",
    "jsonwebtoken": "~0.4.0"
  },
  "description": "Example of Token-based authentication in [AngularJS](http://angularjs.org) with [Express](http://expressjs.com).",
  "main": "auth.server.js",
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/auth0/angular-token-auth.git"
  },
  "keywords": [
    "angular",
    "auth",
    "jwt",
    "express"
  ],
  "author": "",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/auth0/angular-token-auth/issues"
  },
  "homepage": "https://github.com/auth0/angular-token-auth"
}