Token-based authentication

Token-based access is all about sending the token (typically in HTTP headers) with each request instead of a cookie. A simplified token-based workflow looks something like this:

Token-based authentication
Many public APIs (such as facebook and twitter) use token-based authentication. The format of the token, where it goes, and how it is generated depend upon the protocol used and the server implementation. Popular services that use token-based authentication implement the OAuth 2.0 protocol for token generation and exchange.

In a typical token-based authentication setup, the views are available publically, but the API is secured. If the application tries to pull data through API calls without attaching the appropriate token to the outgoing request, the server returns the HTTP 401 Unauthorized status code.

Integrating with a token-based authentication system requires a decent amount of setup on the client side too. Let’s take a simplified example of a Human Resource (HR) system that supports token-based authentication and so that you understand how the authentication workflow works with the Angular application as a client.

The HR system has a page showing the list of employees and a login page. It also has API endpoints to get the list of employees and generate access tokens. The API endpoint that returns the employee list is secured by token-based access.

The workflow starts with the user loading the employee list page. The view is loaded but the API call fails with the server returning HTTP 401 Unauthorized.

On receiving a 401 HTTP error code, the app should respond by either routing the user to a login view (remember this is an SPA) or opening a login popup.

A naive implementation for this could be:

$http.get('/api/employees').then(function (employees) {}, function (error) {
    if (error.status === 401) {
        $scope.unauthorized = true; //Triggers login popup
        //$location.path('/login'); // or redirect to login.
    }
});

The biggest problem with the preceding implementation is that we need to add this code to every controller that requires remote data access as the call may fail. Not very smart!

The correct way to handle this scenario is to use the Angular HTTP interceptor and events. The HTTP interceptor intercepts all 401 errors and raises a global unauthorized event. The interceptor (oversimplified) looks like this:

function ($rootScope, $q, ) {
return {
  responseError: function (error) {
  if (error.status === 401) 
   $rootScope.$broadcast('unauthorized', error);
       return $q.reject(rejection);
     }
   }
}

With such an interceptor in place, any remote request that results in a 401 response is caught by the interceptor and an unauthorized event is raised.

Now it’s just a matter of catching this event in some type of global controller (such as RootController in Personal Trainer) and reacting to it. We use something like this where we set a Boolean variable true, triggering a modal username/password dialog:

$scope.$on("unauthorized",function(evt,err){
    $scope.showLoginDialog=true;
});

The user then enters his/her username/password to authenticate himself with the server. As part of the authentication process, the server validates the credentials, creates a token, and sends it back to the client app.

On the client side, we need to create a service that can send the username/password to the authentication endpoint, and manage the token returned as part of a successful authentication. A sample AuthService implementation might look like this:

angular.module('app').factory("AuthService", function ($q, localStorageService, $http, $rootScope) {
    var service = {};
    service.authenticate = function (userName, password) {
        $http.post('/authenticate', {
            user: userName,
            password: password
        }).then(function (data) {
            localStorageService.add('authToken', data.token);
            $http.defaults.headers.common['Authorization'] = 'Basic ' + data.token;
            $rootScope.$broadcast('userAuthenticated');
        });
    }
    service.useTokenFromCache=function() {
        var token=localStorageService.get('authToken');
        if(token) 
        $http.defaults.headers.common['Authorization'] 
          = 'Basic ' + data.token;
    }    
return service;
});

The AuthService.authenticate function basically invokes the server endpoint to verify credentials, parse the token received from the server, save the token to the local storage/session storage for future use (if there is a page refresh), set the appropriate HTTP header (so that the token is sent on all subsequent requests), and finally broadcast the authentication-complete event.

The following statement actually sets the auth header for all subsequent requests:

$http.defaults.headers.common['Authorization'] 
= 'Basic ' + data.token;

Without this, the complete login process described here will be repeated and we will be stuck in a loop.

This statement also highlights how we can use $http.default.headers to set common headers for all outgoing requests. Angular shows us how to be more specific and that we can alter specific verbs:

$http.defaults.headers.post['some-header']=value;
$http.defaults.headers.put['some-header']=value;
$http.defaults.headers.get['some-header']=value;

Tip

These headers can also be set during the configuration stage of the Angular application, by injecting $httpProvider.

The AuthService.useTokenFromCache function is useful in case of a page refresh scenario. This function can be invoked during the module run phase:

angular.module('app').run(function(AuthService){
    AuthService.useTokenFromCache();
});

If the token is found in local storage, the login workflow is avoided.

Note

If the token in location storage expires (remember, each token has a lifetime) the authentication will fail with HTTP 401, and we will need to repeat the login flow.

The walkthrough here just outlines one mechanism to send a token to the server but the process may vary based on the server stack too. Always refer to the backend/server documentation before implementing a token-based authentication in Angular.

Two of the notable modules/libraries that we should mention here are:

  • angular-http-auth (https://github.com/witoldsz/angular-http-auth): The preceding walkthrough can be easily implemented using this library, as it already has the http interceptor and events to support what we have described earlier.
  • ng-token-auth (https://github.com/lynndylanhurley/ng-token-auth): This is a comprehensive library that does much more than just authentication.

Both these libraries can help us get started with token-based authentication.

We have taken care of authentication but what about authorization? Once the user context is established we still need to make sure the user is only able to access the parts that he/she is allowed to. Authorization is still missing.