;

Cookies vs Tokens. Getting auth right with Angular.JS

Introduction

There are basically two different ways of implementing server side authentication for apps with a frontend and an API:

Token based vs. Cookie based

The following diagram explains how both of these methods work.

cookie-based vs. modern token-based auth

What are the benefits of using a token-based approach?

What's JSON Web Token? JSON Web Token (JWT, pronounced jot) is a relatively new token format used in space-constrained environments such as HTTP Authorization headers. JWT is architected as a method for transferring security claims based between parties.

Asuming you have a node.js app, below you can find the components of this architecture.

Server Side

Let's start by installing express-jwt and jsonwebtoken:

$ npm install express-jwt jsonwebtoken

Configure the express middleware to protect every call to /api.

var expressJwt = require('express-jwt');
var jwt = require('jsonwebtoken');

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

app.use(express.json());
app.use(express.urlencoded());

The angular app will perform a POST through AJAX with the user's credentials:

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.send(401, '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 });
});

GET'ing a resource named /api/restricted is straight forward. Notice that the credentials check is performed by the expressJwt middleware.

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

Angular Side

The first step on the client side using AngularJS is to retrieve the JWT Token. In order to do that we will need user credentials. We will start by creating a view with a form where the user can input its username and password.

<div ng-controller="UserCtrl">
  <span></span>
  <form 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>

And a controller where to handle the submit action:

myApp.controller('UserCtrl', function ($scope, $http, $window) {
  $scope.user = {username: 'john.doe', password: 'foobar'};
  $scope.message = '';
  $scope.submit = function () {
    $http
      .post('/authenticate', $scope.user)
      .success(function (data, status, headers, config) {
        $window.sessionStorage.token = data.token;
        $scope.message = 'Welcome';
      })
      .error(function (data, status, headers, config) {
        // Erase the token if the user fails to log in
        delete $window.sessionStorage.token;

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

Now we have the JWT saved on sessionStorage. If the token is set, we are going to set the Authorization header for every outgoing request done using $http. As value part of that header we are going to use Bearer <token>.

sessionStorage: Although is not supported in all browsers (you can use a polyfill) is a good idea to use it instead of cookies ($cookies, $cookieStore) and localStorage: The data persisted there lives until the browser tab is closed.

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;
    },
    response: function (response) {
      if (response.status === 401) {
        // handle the case where the user is not authenticated
      }
      return response || $q.when(response);
    }
  };
});

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

After that, we can send a request to a restricted resource:

$http({url: '/api/restricted', method: 'GET'})
.success(function (data, status, headers, config) {
  console.log(data.name); // Should log 'foo'
});

The server logged to the console:

user foo@bar.com is calling /api/restricted

The source code is here together with an AngularJS seed app.

What's next?

In upcoming posts we will revisit:

UPDATE: we published two new blog posts

Bottom Line

When building Single Page Applications, consider using a token-based authentication design over cookie-based authentication. Leave a comment or discuss on HN. Learn more about AngularJS Authentication.

Aside: how it works with Auth0?

Auth0 issue JSON Web Tokens on every login. That means that you can have a solid identity infrastructure, including Single Sign On, User Management, support for Social, Enterprise and your own database of users with just a few lines of code. We implemented a tight integration with Angular: https://github.com/auth0/auth0-angular

More about Auth0 and Angular: https://github.com/auth0/auth0-angular