When we talk about security in a web application or in general, the main thing we want to accomplish is to prevent malicious behavior and stay safe in a not-so-friendly environment. The Internet is exactly that. Our client code is completely exposed, and everyone with a browser can have a look. Minifying the code makes it less readable, but it’s still there. However, we can protect ourselves using good practices on both server and client sides. Angular helps us do that on the client side.

AngularJS native security

While Angular 2 (or just Angular) is taking the stage by storm, there are still plenty of projects that use the first version, AngularJS. Just to be clear, in this post we will be discussing security in AngularJS. The basic information found in the official documentation help us to compile a short list of advices:

  • keep AngularJS up to date,
  • avoid generating templates on the server using user-provided content and
  • generally avoid parsing user-provided content as AngularJS code through the use of built-in functions.

AngularJS also provides protection from certain types of attacks if the server is compatible and properly configured. Firstly, for those who do not know, cookies are domain-specific data that persists when making requests. If cookies are being used for authorization between the server and the client, AngularJS provides protection from XSRF attacks by using the double-submit cookie defense pattern. For example, we can set up our MVC or WebAPI server to send out a cookie called XSRF-TOKEN. When AngularJS detects that cookie in the response header, it creates a custom X-XSRF-TOKEN header with the value of the XSRF-TOKEN cookie. The two values are then compared on the server, and if they match, the authorization is valid. The attacker cannot trick the browser to send out the custom header since it is something AngularJS sends when making legitimate requests from within the app.

Token based authorization

There are a couple of ways of authorizing communication between client and server. For one, we can use cookies, but there are plenty of ways to exploit them if we’re not careful. The most common way is through the use of tokens, which are encoded bits of data containing authorization data, information about the user, expiration time, and whatever else we choose to put in it. A token is not encrypted, but encoded, so we need to be careful not to put sensitive data (like passwords). We will be looking at an example that uses tokens generated on the server.

A token is most commonly generated when a user logs into a web application. Once we receive it back from the server, we want to store and keep it for as long as the user is making the requests through the application. One of the ways to do this is to store it in the browser’s local storage. Here is how we can do it in AngularJS:

function storeToken(token) {
    token = angular.extend(token, {
        expirationDate: new Date(token['.expires']).getTime()
    });

    $localStorage.token = token;

    return token;
}

We can see a couple of things here. First, we extend the token object with an expirationDate property using the expiration value already set on the token by the server. This is how tokens work - after they expire, a new one is requested from the server. Extending this additional property is not vital, but it will help us when testing if the token has expired. The most important part is using AngularJS’s $localStorage service to access the browsers local storage, and save the token. What we want to do then is attach the token value to the header in every request we make to the server. Getting the token back from $localStorage is pretty straightforward, but we can do an additional check now:

function isTokenValid(token) {
    if (new Date().getTime() > token.expirationDate) {
        return false;
    }

    return true;
}

function getToken() {
    var token = $localStorage.token;
    if (token) {
        if(isTokenValid(token)) {
            return token.access_token;
        }
    }

    return null;
}

In the first method isTokenValid, we are testing whether the expiration date on the token is earlier than the current time and if it is, we want to say that the token is invalid. The second method getToken retrieves the token from the $localStorage first, and if it’s valid, returns the authorization data that will be inserted into the header of a request. We can place the token in the header of every request by using another Angular construct called interceptor. A typical implementation of an interceptor service would be something like this:

angular.module('myApp').service('exampleInterceptor', function ($injector) {
    //implementation of all interceptor methods is optional as stated in the docs
    return {
        request: function (config) {
            var token = $injector.get('exampleSecurityService').getToken();
            if(token) {
                config.headers.Authorization = 'Bearer' + token;
            }

            return config;
        }
        requestError: //handle error responses, e.g. authorization failed
    }
});

angular.module('myApp').config(function($httpProvider){
    $httpProvider.push('exampleInterceptor');
});

This will ensure every request we make will get intercepted before being sent out, and an Authorization header will be placed on it. What we can do next is check for the token on every state change like this:

$rootScope.$on('$stateChangeStart', function (evt, to, params) {
    var token = exampleSecurityService.getToken();
    if (!token) {
        evt.preventDefault();
        // logout the user
    }
    // additional permission based logic
});

This checks for token and its validity on every state change. If the token is missing or invalid, the user will be logged out. We can also place some permission-based logic in here - we can control that users cannot view what they don’t have permission for. This code is usually placed in the run block of the module.

Route redirecting

If we are using the ui-router framework, there is another thing we can do to ensure that users cannot go to non-existing routes; something like this:

angular.module('myApp').config(function ($urlRouterProvider) {
    $urlRouterProvider
        .when('/', '/homepage')
        .otherwise(function ($injector) {
            var state = $injector.get('$state');
            if(state.href(state.current.name)) {
                state.go(state.current.name);
            } else {
                state.go('homepage');
            }
        })
});

There are various ways to do that, this is just one example. What we are doing here is - while the user is in a valid state and enters an invalid URL, we redirect him back to the original state he was in. The else scenario happens when a user types in a faulty URL when first connecting to our application, so we redirect him to the homepage. We don’t need to check if the user is authorized if we are doing it in our run block.

This has been one take at handling security in an AngularJS application and certainly not the only or the best one for that matter. When building your own app, you will tailor it to your needs. I hope this will be helpful to some of you. Until next time, cheers.

More articles

Your opinion matters!

comments powered by Disqus