introduction

SPA (Single Page Applications) are web applications commonly developed in HTML, JavaScript and CSS. The user loads the initial page then all the needed resources are loaded dynamically via XHR and inserted into the DOM without reloading the page.

Developing SPA application can be extremely exciting, but we should always stay focused on securing all the app’s possible leaks.

Managing user authentication via RIA (Rich Interface Applications) is similar to standard web Client Server Application. Usually, in Standard web apps the authentications is fully managed by the used framework (Laravel Auth, Spring Security …) and it’s pretty simple:

  • After a valid user authentication, the server sends a secret token that is stored server side in the
    Server Session or the DataBase, and client side in the cookies.
  • Each time the client (browser) asks for a resource, it sends the secret token in the header.
  • The server compares the token with the one it has, if it’s valid, it responds and sends back a new token that will be used for the next request.
  • If there is a problem with the token the user is redirected to the Login form.

RIA apps uses XHR requests to retrieve data from the server via REST services, it can’t manage authentication exceptions, because the tokens verification are made server side.
In our case we will be developing a REST API that should use the Framework Authentication features and manage Authentication issues (disconnects, session expiry, hacks, etc …) and redirect the user to the login form.

In this article we’ll be using :

    • Laravel 4, which is one of the coolest PHP Framework available, the fourth version is officially released !!! (I waited too long for this release).

 

    • AngularJS, the JavaScript MVW framework that is super-powered by google (which is enough for me to think that it is a great tool).

 

    • a DataBase (for my self I’m using a MySQL) you can find supported DataBases List on the Laravel Documentation.

 

We will develop an E2E (end to end) authentication feature on a SPA application.
Sources can be found on https://github.com/neoxia/laravel4-angularjs-security.

In this article we assume that you have some knowledge in MVC frameworks, PHP, JavaScript (it will be great if you already know AngularJS and Laravel4, but this is not necessary, I’ll try to make it easy for everyone). In case of difficulties you can consult http://laravel.com/docs and http://docs.angularjs.org/ both contain a complete documentation for both Frameworks.
No more introducing, it’s time to jump into some serious s*** 😀

Preparing laravel

install laravel

I’m not going to do a tutorial on “Installing Laravel”, everything is already well explained in the documentation : http://laravel.com/docs/installation

Add angular to the project

add angular structure

Inspired by https://github.com/angular/angular-seed and assuming that we will not be doing tests in this app, we decided to apply this structure to our laravel project. Under laravel’s public folder create those folders/files :
LARAVEL-ANGULAR
I added Twitter/Bootstrap, so the App looks nice. http://twitter.github.io/bootstrap/

The angularJS files can be downloaded from http://angularjs.org

In case of a project remember to add the files/folders for testing, Angular with Karma provides a really powerful way for unit and E2E testing.

Test it

Let’s create our first view, the entry point for our SPA:

We need to modify the Laravel route to wire the “/” URL to the first view, go to app/route.php and change the existing route to that:

Route::get('/', function()
{
	return View::make('index');
});

Let’s create our new view: create index.php in app/views

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Laravel4 AngularJS Authentication and security</title>
    <link href="app/css/bootstrap.min.css" rel="stylesheet">
    <link href="app/css/bootstrap-responsive.min.css" rel="stylesheet">
    <link href="app/css/app.css" rel="stylesheet" />
</head>
<body ng-app=”myApp”>

<div class="container" ng-view></div>

<script src="app/lib/angular/angular.min.js"></script>
<script src="app/lib/angular/angular-resource.min.js"></script>
<script src="app/lib/angular/angular-sanitize.min.js"></script>
<script src="app/js/app.js"></script>
<script src="app/js/controllers.js"></script>
<script src="app/js/directives.js"></script>
<script src="app/js/filters.js"></script>
<script src="app/js/services.js"></script>
</body>
</html>

The Angular ng-app directive defines the scope where the Angular module will operates.

The Angular ng-view directive means that angular will inject the loaded partials into this &ltdiv&gt.
To stop Angular from showing the error: Uncaught Error: No module: myApp, just create the myApp module in public/app/js/app.js.

Now let’s create the first controller in public/app/js/controller.js, the angular route so we will wire the controller and the login.html partial to the “/” URL in app.js so we use $routeProvider to configure our routes, so if you are in the ‘/’ folder you will get the login form which uses loginController as a Controller, and other URLs will be redirected to ‘/’.

// app.js
angular.module("myApp",[ ])

.config(['$routeProvider',function($routeProvider){
        $routeProvider.when('/',{templateUrl:'app/partials/login.html', controller: 'loginController'})
        $routeProvider.otherwise({redirectTo:'/'})
}])

// controller.js
angular.module("myApp").controller('loginController',function($scope){
    //controller code will be inserted here
})

Create the login partial

Now we create a simple bootstrap login form: create the login.html into public/app/partials/

<h1>Login Form</h1>
<div class="alert" ng-show="flash" ng-bind="flash"></div>
<form class="form-horizontal" ng-submit="login()">
    <div class="control-group">
        <label class="control-label" for="inputEmail">Email</label>
        <div class="controls">
            <input type="text" id="inputEmail" placeholder="Email" ng-model="email" required>
        </div>
    </div>
    <div class="control-group">
        <label class="control-label" for="inputPassword">Password</label>
        <div class="controls">
            <input type="password" id="inputPassword" placeholder="Password" ng-model="password" required>
        </div>
    </div>
    <div class="control-group">
        <div class="controls">
            <button type="submit" class="btn">Sign in</button>
        </div>
    </div>
    </div>
</form>

Let’s begin with the explaining the ng-model directive: it means that Angular will do two-way data binding on this input.
The ng-submit directive will call the login() method when the form is submitted.

The ng-show directive displays the element only if the value exists.
The ng-bind is another way to bind a variable on Angular, same as {{ variable }}. I prefer using ng-bind because in some old or slow browsers we can on load see the brackets before the JS interpreter starts replacing the {{value}} by its values in the controller.

Now that we’ve linked our view form with the controller and configured the route let’s prepare our Laravel Server.

Configure DB Create Services on Laravel

important : be sure to correctly configure your Laravel app/config/database.php so the app could connect to your database.

Now we have to create a Migration for the table Users: artisan offers a simple way to scaffold the Migration and the controller.

$ php artisan migrate:make create_users_table --table=users --create

This command created a new Migration file in app/database/migrations/DATE_create_users_table.php in which we can find the up() method that will create the table. edit it this way:

public function up()
	{
		Schema::create('users', function(Blueprint $table)
		{
			$table->increments('id');
                                 $table->string('email')->unique();
                                 $table->string('password');
			$table->timestamps();
		});
	}

Now that the Table Schema is ready, it needs some data. Laravel provides a nice way to seed your DB, no more Dump.sql, you have to create this file: /app/database/seeds/UserTableSeeder.php

class UserTableSeeder extends Seeder {
    public function run(){
        DB::table('users')->delete();

        User::create(array(
            'email' => 'admin@admin.com',
            'password' => Hash::make('admin')
        ));
    }
}

This will create a new user identified by ‘admin@admin.com’ with password ‘admin’ (don’t panic, the password will be hashed).

In the /app/database/seeds/DatabaseSeeder.php add this to the run() method:

public function run()
	{
		Eloquent::unguard();
		$this->call('UserTableSeeder');
	}

All the configuration done, let’s run this Migration and Seed it’s table.

$ php artisan migrate
$ php artisan db:seed

Now that we’ve created the Tables let’s create the authentication REST service.

Create auth service:

Because Laravel comes with a User model setup, let’s directly start creating the REST service.

Create the Service Controller:

Once again artisan comes with a simple way to scaffold your controller:

php artisan controller:make AuthenticationController

Now that your Controller is created go to /app/controllers/AuthenticationController.php add this to the Store() method (which is the method invoked when a POST is done on this Controller)

public function store()
{
        $credentials = array(
            'email' =>  Input::get('email'),
            'password' =>  Input::get('password'));
        if ( Auth::attempt($credentials) ) {
            return Response::json([
                    'user' => Auth::user()->toArray()],
                202
            );

        }else{
            return Response::json([
                    'flash' => 'Authentication failed'],
                401
            );
        }
}

And the Logout in the index() method:

public function index()
{
        Auth::logout();
        return Response::json([
                'flash' => 'you have been disconnected'],
            200
        );
}

The Auth service from Laravel is used to easily manage the Authentication and the Sessions variables.
Let’s add new Routes to /app/routes.php so our services will be accessible.

Route::group(array('prefix' => 'service'), function() {

    Route::resource('authenticate', 'AuthenticationController');
});

The service will be accessible on http://localhost/service/authenticate via GET and POST.

Finish the Front End authentification:

Now that the service is ready, back to Angular. We will use ‘angular.resources’ to do XHR requests on the service and ‘angular.sanitize’ to avoid script injection. First we’ll add the dependencies so it will be injected into our app.

Then we will also add a new route /home, a page where we will be redirected if you are authentified.
We create the service to call the XHR on the server into public/app/js/services.js:

//app.js
angular.module("myApp",['ngResource','ngSanitize'])
   .config(['$routeProvider',function($routeProvider){
        $routeProvider.when('/',{templateUrl:'app/partials/login.html', controller: 'loginController'})
        $routeProvider.when('/home',{templateUrl:'app/partials/home.html', controller: 'homeController'})
        $routeProvider.otherwise({redirectTo:'/'})
    }])

//controllers.js
angular.module('myApp')
    .controller('loginController',function($scope,$sanitize,$location,Authenticate){
        $scope.login = function(){
            Authenticate.save({
                'email': $sanitize($scope.email),
                'password': $sanitize($scope.password)
            },function() {
                $scope.flash = ''
                $location.path('/home');
            },function(response){
                $scope.flash = response.data.flash
            })
        }
})
    .controller('homeController',function($scope,$location,Authenticate){
        $scope.logout = function (){
            Authenticate.get({},function(){
                $location.path('/');
            })
        }
})

//services.js
angular.module('myApp')
    .factory('Authenticate', function($resource){
        return $resource("/service/authenticate/")
    })

public/app/partials/home.html

<h1>Home Page</h1>
<button type="submit" class="btn" ng-click="logout()">Sign out</button>

The ng-click directive will call the function logout() in the loginController when you click on the input.

We use Angular-sanitize to secure our app against Script-Injection, this will escape the input returned String to avoid injecting script tags into the input.
We are secured against SQL-Injection because Laravel auto escapes strings with Eloquent ORM.
Now go test it 😀 At this point everything looks great but we still can access to /home just by going to it’s url http://localhost:8000/#/home.

Secure /home:

A simple way to do this is to store a variable, in one of the data stores provided by the browsers:

    • the Cookies (can be modified with angular-cookies). Sent each time to the server and has a specified
      expiration date.
    • the LocalStorage has no expiration date, so in case of a Bug the user can be stuck until he empties his LocalStorage
    • the sessionStorage stores data for one session.

In our case the sessionStorage seems to be the best choice. We just add a key/value “Authenticated=true” when the user connects and change it to false when he logs out.

angular.module('myApp')
    .controller('loginController',function($scope,$sanitize,$location,Authenticate){
        $scope.login = function(){
            Authenticate.save({
                'email': $sanitize($scope.email),
                'password': $sanitize($scope.password)
            },function() {
                $scope.flash = ''
                $location.path('/home');
                sessionStorage.authenticated = true;
            },function(response){
                $scope.flash = response.data.flash
            })
        }
})
    .controller('homeController',function($scope,$location,Authenticate){
        if (!sessionStorage.authenticated){
            $location.path('/')
        }
        $scope.logout = function (){
            Authenticate.get({},function(){
                delete sessionStorage.authenticated
                $location.path('/');
            })
        }
})

But the user can edit and change it’s sessionStorage, so this solution is not good enough.
We will be securing the responses from the server by adding Laravel filters on services that needs to be secured. We will also see later a better way to secure our client side pages.

Add a movies service and Secure it:

Let’s create my SECRET list of movies !!! It should be only accessible to authenticated users.

Same as we’ve done for the Users Controller and the Authentication Service create a service that sends a list of movies (or anything you want) — remember all sources are on github (in case of difficulties copy/past).

The only difference is that the Movie Model doesn’t exists by default (like the user Model). You have to create it:
/app/models/Movie.php :

class Movie extends Eloquent{}

To secure the service we are going to add a Laravel Filter on /app/filters.php:

Route::filter('serviceAuth', function(){
    if(!Auth::check()){
        return Response::json([
            'flash' => 'you should be connect to access this URL'
        ], 401);
    }
});

And in the MovieController.php add the filter into the class constructor, thereby it will be applied before any treatment:

public function __construct()
    {
        $this->beforeFilter('serviceAuth');
    }

Right now the server will not send our secret informations to non authenticated users.
However we are missing something, if some weird hacker changes the sessionStorage value, he can navigate on the SPA but he will not get the server response.

Create the 401 interceptor:

Angular provides a Response interceptor, $http requests can be intercepted with $httpProvider and we can send the user to the login page if he gets a 401 server Response.

angular.module("myApp",['ngResource','ngSanitize'])
    .config(['$routeProvider',function($routeProvider){
        $routeProvider.when('/',{templateUrl:'app/partials/login.html', controller: 'loginController'})
        $routeProvider.when('/home',{templateUrl:'app/partials/home.html', controller: 'homeController'})
        $routeProvider.otherwise({redirectTo:'/'})
    }]).config(function($httpProvider){
        var interceptor = function($rootScope,$location,$q,Flash){
        var success = function(response){
            return response
        }
        var error = function(response){
            if (response.status == 401){
                delete sessionStorage.authenticated
                $location.path('/')
                Flash.show(response.data.flash)
            }
            return $q.reject(response)
        }
            return function(promise){
                return promise.then(success, error)
            }
        }
        $httpProvider.responseInterceptors.push(interceptor)
    })

The idea is that we intercept all the XHR requests and check if it’s status is 401. In this case, it removes the value of the sessionStorage and redirect the user to the login page.
There are a lot of modules already developped that you can find in http://ngmodules.org/, which is a great source of Angular Modules already developped by the comunity.
And what we’ve done before already exists as a module with more features (queuing requests and send them again after authentication) http://ngmodules.org/modules/http-auth-interceptor/

Secure the service against CSRF:

CSRF (Cross-Site Request Forgery) is an attack that consists of calling your REST service from another domain, via a <script src=”…”> tag which is not concerned by the CrossDomain Policy. The request will be sent to the server with the Session Cookie.

The server will respond to the request and the hacker can read a part or the totality of the Response by redefining the Array() method (more details on wikipedia).

So a way to secure your app against this, is to keep track of a token, not stored into the cookie, and it will be sent with every Request.

Inject the CSRF into the index.php

Laravel provides a csrf_token(), an easy way to send this token to the user is to put it into the code of the index.php (the entry point for the SPA). add this code after your javascript calls.
app/views/index.php

<script>
    angular.module("myApp").constant("CSRF_TOKEN", '<?php echo csrf_token(); ?>'); 
</script>

Add the header to the $http

Now, we have to add it to the header so it can be checked server side on the requests. Then, add the run method to your module, which will be casted on the start of the module.

angular.module("myApp").run(function($http,CSRF_TOKEN){
        $http.defaults.headers.common['csrf_token'] = CSRF_TOKEN;
    })

Create the CSRF filter

Back to Laravel to create another filter that will check the CSRF_token (you can add the CSRF check to the same filter that we created for the authentication check):
app/filters.php

Route::filter('serviceCSRF',function(){
    if (Session::token() != Request::header('csrf_token')) {
        return Response::json([
            'message' => 'I’m a teapot !!! you stupid hacker :D'
        ], 418);
    }
});

and finally add the filter to the controllers as we’ve done before into the _construct method:

public function __construct()
    {
        $this->beforeFilter('serviceAuth');
        $this->beforeFilter('serviceCSRF');
    }

That’s it, don’t hesitate to comment on the blog (just say hi) and share if you like tea.