Skip to content
Jim Cook edited this page May 16, 2013 · 3 revisions

Different than Normal Website Security

Handling security for single page applications is a bit strange at first. There are no page refreshes besides the first one, so from the server's perspective there is one initial web request for the page content. The remaining interaction with the server comes in the form of these two requests:

Partials

A web site template which is initially loaded from the server and subsequently cached on the client.

API calls

A series of REST requests made by the server to perform some type of CRUD functionality.

Spring Security Capabilities

Spring Security will add a Servlet Filter which investigates every web request originating from the client. Under the default configuration, this means all css, images, javascript and even the index.html are checked against the security filter. The actual spring security configuration controls the urls which are fetched from the server and whether each url is subject to scrutiny or not.

    <!-- No security on any of these urls -->

    <http pattern="/lib/**" security="none"/>
    <http pattern="/img/**" security="none"/>
    <http pattern="/js/**" security="none"/>
    <http pattern="/css/**" security="none"/>
    <http pattern="/" security="none"/>
    <http pattern="/partials/home.html" security="none"/>


    <http auto-config='true'>
        <intercept-url pattern="/partials/signin.html" access="ROLE_USER" />
        <intercept-url pattern="/api/**" access="ROLE_ANONYMOUS" />
    </http>

With this configuration, the basic display of our home page will occur without any security interference whatsoever. Only when the user navigates to the signin url, which will in turn load the partials/signin.html file, does the security kick in. Instead of returning the expected signin.html file, the security filter returns a login form. AngularJS will show the login form as expected, but it will also cache it. Heaping more problems on the pile, the login form does a typical form submit, and will issue a redirect of the entire page to the original desired url of partials/signin.html.

Clearly this is very nice behavior when we are building traditional web applications, but in an AJAX-driven single page app it is very undesirable. We have to forego any expectations that url-based security will be applied to page urls. Instead we turn our attention to AJAX requests.

Securing AJAX Calls

As we have seen, an AngularJS application only makes a single web request for content, then all other tasks are to retrieve data via AJAX requests. All AJAX requests emanating from the client in an AngularJS application will pass through the $http service at some point. This provides us with a technique to ensure that all requests have an opportunity to include authentication information at a very low level. Fortunately, ANgularJS has provided a response interceptor framework which gives us a nice spot to do something about security when dealing with AJAX requests.

var mod = angular.module( 'spring-security', [] );

mod.config( ['$httpProvider', function ( $httpProvider ) {
    var authInterceptor = ['$q', function ( $q ) {
        var success = function ( response ) {
            return response;
        };

        var error = function ( response ) {
            return response;
        };

        return function ( promise ) {
            return promise.then( success, error );
        }
    }];


    $httpProvider.responseInterceptors.push( authInterceptor );
}] );

The $http service returns a promise, so as long as our interceptor also returns a promise, it can do anything to the result as necessary. Since we are focused on security, there are a few tasks that we will need to contend with when AJAX requests are made.

  1. Security Authentication - making sure the person has properly signed in when necessary and being able to ensure their username and password are correct.
  2. Security Authorization - ensure that the person has access to the API endpoint they are accessing and are the appropriate person to be taking a particular action. (ie. users can access the profile endpoint, but only edit their profile)
  3. Timeout - often overlooked in web applications is support for redirecting the user to sign in again if their credentials have expired and a new AJAX request is made. Because AJAX apps like ours secure only the AJAX calls, a session expiring will be easily routed to the sign in screen.
  4. Remember me - we do not want to prompt the user to re-authenticate if they have chosen for their authentication to be remembered for an extended period of time.

Implementing the Client

Communication between the client and server will pass the user's security credentials on every API request. This is called BASIC Authentication and there is no encryption of this data, only light encoding using Base64. This is why all of our AJAX requests to the server will take place using SSL encryption.

It is not necessary to pass credentials to obtain HTML content via the /partials/*.html url, so we can choose to not require authentication on the server for these urls. We will also have to write our interceptor such that credentials are not passed in the header for these requests. There may be other AJAX requests for third-party services like Twitter that we also want to exclude from authenticating.

It's a bit obvious we will need to replace the success and error handlers with something else in order to support authentication. At least, the error handler will need to be replaced. If the success handler is called, there isn't really anything for us to do but return the result. There are a few possible error conditions we will need to deal with.

401 Unauthorized

Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource.

403 Forbidden

The request was a valid request, but the server is refusing to respond to it. Unlike a 401 Unauthorized response, authenticating will make no difference. On servers where authentication is required, this commonly means that the provided credentials were successfully authenticated but that the credentials still do not grant the client permission to access the resource (e.g. a recognized user attempting to access restricted content).

500 Internal Server Error

A generic error message, given when no more specific message is suitable. Note that I am including the 500 error here, but the handling of these errors should be moved to another interceptor.

The description of each 40x error spells out the situation precisely. A 401 error can be retried once the user successfully authenticates. A 403 exception is an authorization failure, and no amount of retries will result in a successful result. This raises an important implementation point for us. Any requests that result in a 401 status will be retained and automatically replayed once the user has successfully authenticated.

The New Error Handler

Our error handler uses a service object called httpBuffer to store a list of http requests and their associated promises which have failed. The beauty of the Promise object is the original pieces of code just sit and wait for this authentication step to take place. When it completes, the httpBuffer retries its requests (which are stored in response.config) and the original callers are none the wiser that any authentication took place.

    var error = function ( response ) {
        // If the user is not authenticated, we will track the request to be replayed
        if ( response.status === 401 ) {
            var deferred = $q.defer();
            httpBuffer.append( response.config, deferred );
            $rootScope.$broadcast( events.EVENT_LOGIN_REQUIRED );
            return deferred.promise;
        }
        // No need to replay the requests that result in a 403
        if ( response.status === 403 ) {
            $rootScope.$broadcast( events.EVENT_NOT_AUTHORIZED );
        }
        return $q.reject( response );
    };

In the case of a 401 error, we push the request into the httpBuffer, but for a 403 error we do not. We are also raising events on the $rootScope to provide a level of decoupling between our handling of authentication and keeping this portion of the client-side library clean and reusable. Now, let's deal with the server code.

Server-Side Implementation

As we saw in the first section of this document, Spring Security will attempt to send redirect codes back to the browser when things like authentication are required. We need to override Spring's tendencies and send back special result tokens to which our implementation can respond.

todo: explain the details of the Spring code

Clone this wiki locally