Alexander Beletsky's development blog

My profession is engineering

Simple Authentication for Angular.js App

So, you are building pure client side application that works against REST API. The client and server are completely decoupled and typically deployed separately of each other.

API’s have one or another way of authenticating it’s users. It could be some simple flows, like basic authorization or more complex ones as OAuth/OAuth2. But at the very end you have token that placed either as cookie value or HTTP request header parameter. API is then responsible to check the token for validity and if it’s not valid respond with 401.

Configure the Routes

First we need to have /login router where user is redirected in case of unauthorized access.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'use strict';

var app = angular.module('dashboardApp', [
  'ngCookies',
  'ngResource',
  'ngSanitize'
]);

app.config(function ($routeProvider, $locationProvider, $httpProvider) {
  $httpProvider.responseInterceptors.push('httpInterceptor');

  $routeProvider
      .when('/', { templateUrl: 'views/dashboard.html', controller: 'dashboard' })
      .when('/login', { templateUrl: 'views/auth.html', controller: 'auth' })
      .otherwise({ redirectTo: '/' });

  $locationProvider.html5Mode(true);
});

app.run(function (api) {
  api.init();
});

Authentication Controller and View

The authentication controller is simple module. It’s responsible for sending user credentials to server and handle the response. If server authenticates user, it would return the value of access token in .token attribute. Otherwise, user have to be notified that something went wrong.

Btw, in likeastore/seismo-dashboard I’ve tried to use the model based on GitHub personal tokens instead of passwords, that simplifies server a bit allowing it to do not store and sessions, hashed passwords etc. If you interested, take a look at likeastore/seismo/source/server.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
'use strict';

angular.module('dashboardApp').controller('auth', function ($scope, $location, $cookieStore, authorization, api) {
  $scope.title = 'Likeastore. Analytics';

  $scope.login = function () {
      var credentials = {
          username: this.username,
          token: this.token
      };

      var success = function (data) {
          var token = data.token;

          api.init(token);

          $cookieStore.put('token', token);
          $location.path('/');
      };

      var error = function () {
          // TODO: apply user notification here..
      };

      authorization.login(credentials).success(success).error(error);
  };
});

As you can see, it just delegates the call to authorizationn service, which is very simple wrap of $http.

1
2
3
4
5
6
7
8
9
10
11
'use strict';

angular.module('dashboardApp').factory('authorization', function ($http, config) {
  var url = config.analytics.url;

  return {
      login: function (credentials) {
          return $http.post(url + '/auth', credentials);
      }
  };
});

Ones server responds with success, controller will place the token to cookie.

The view is just a form with binded ng-submit event to call auth.login() function of controller.

1
2
3
4
5
6
7
8
9
<div class="login-panel">
  <h1>Welcome to Analytics.</h1>
  <p>We are using GitHub for authorization. Please obtain your personal token and use it to sign in.</p>
  <form class="pure-form" ng-submit="login()">
      <input type="password" class="pure-input-1-3" placeholder="Personal Token..." name="token" ng-model="token" required/>

      <button type="submit" class="pure-button pure-button-primary">Sign in</button>
  </form>
</div>

HTTP Interceptor

In case of any API call returns 401 we have to redirect user to login page. Angular’s HTTP interceptor is great for that job. As you can see from app.js above, it’s been pushed to pipe here:

1
$httpProvider.responseInterceptors.push('httpInterceptor');

The interceptor implementation itself,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'use strict';

angular.module('dashboardApp').factory('httpInterceptor', function httpInterceptor ($q, $window, $location) {
  return function (promise) {
      var success = function (response) {
          return response;
      };

      var error = function (response) {
          if (response.status === 401) {
              $location.url('/login');
          }

          return $q.reject(response);
      };

      return promise.then(success, error);
  };
});

Placing Token to HTTP Headers

Finally, we need to supply that token as HTTP header parameter to all API calls that client issues. Again, you might notice in app.js there is an API initialization call.

1
2
3
app.run(function (api) {
  api.init();
});

API initialization services looks like that,

1
2
3
4
5
6
7
8
9
'use strict';

angular.module('dashboardApp').factory('api', function ($http, $cookies) {
  return {
      init: function (token) {
          $http.defaults.headers.common['X-Access-Token'] = token || $cookies.token;
      }
  };
});

This authentication model is very easy to integrate into any existing apps and just keep in mind while creating new ones.