Chapter 10. AngularJS Security

You might wonder why we are covering security in a book on AngularJS. Well, quite simply, security is one of the most important and most challenging tasks faced by an AngularJS developer. It’s not that the developer is actually responsible for implementing the security layer—that is not the case at all—but it is very important for an AngularJS developer to understand the role that AngularJS plays in the overall security model of an application or website.

You should never attempt to implement an independent client-side security layer in an AngularJS application, or any other JavaScript application for that matter. Security should always be implemented on the backend services where the data resides. That is the only safe place to implement a security layer.

Remember the user has full access to the JavaScript running in the browser. As I said before, our AngularJS application runs in the user’s browser on the user’s hardware. The user can save the JavaScript locally and easily make modifications circumventing any security layer implemented by an unsuspecting JavaScript developer.

With that in mind, there are several rules that AngularJS developers and backend developers need to remember. Although actually implementing the security layer is not usually the job of an AngularJS developer, it is often a collaborative effort for all developers involved in a project. The following rules should always be considered:

  1. Always use SSL to communicate with REST services that contain private data (HTTPS).
  2. Always use some type of authentication on each REST service call that contains private data (Basic Authentication, for example).
  3. Never hold REST service authentication status in a session variable on the server. Doing that opens your server-side application up to cross-origin attacks and other serious security concerns.
  4. Never implement a Cross-Origin Resource Sharing (CORS) layer that returns * as the list of allowed domains. For example, (Access-Control-Allow-Origin: *) would allow all domains to make cross-origin calls to the REST services on the site. Doing that circumvents the browser’s CORS security implementation completely.
  5. Always make sure that any JavaScript that may get injected inside a JSON property does not get executed on the server side. This design flaw is at the core of the NoSQL injection attack, where JavaScript functions are injected in the JSON request of a service and unknowingly executed by the server, in order to breach the security of a NoSQL database.

Always remember that any security-related JavaScript code can be viewed and modified by the user. While most modern browsers do offer built-in security, JavaScript developers should never rely on the browser for security. The responsibility for security rests entirely on the shoulders of the backend service developers. With that said, I will show some techniques for developing AngularJS applications that work well with a security layer implemented properly in the backend services.

Adding a Login Controller

Now we need to add a login controller. Open your editor and add the code shown next to the bottom of the controllers.js file. Notice that we inject the new Login service and the setCreds business logic service that we developed back in Chapter 8. We also inject the $location service to allow us to redirect the user once authenticated. The new controller has a submit method that is attached to the scope. Attaching the method to the scope allows us to call the method from inside the login template. We build the JSON request that gets passed to the service in the variable named postData, using the scope properties submitted by the form:

/* chapter10/controllers.js excerpt */

blogControllers.controller('LoginCtrl', 
  ['$scope', '$location', 'Login', 'setCreds',
    function LoginCtrl($scope, $location, Login, setCreds) {
      $scope.submit = function(){
      $scope.sub = true;
      var postData = {
        "username" : $scope.username,
        "password" : $scope.password
      };

    Login.login({}, postData,
      function success(response) {
        console.log("Success:" + JSON.stringify(response));
        if(response.authenticated){
          setCreds($scope.username, $scope.password)
          $location.path('/');
        }else{
          $scope.error = "Login Failed"
        }
      },
      function error(errorResponse) {
        console.log("Error:" + JSON.stringify(errorResponse));
      }
    );
  };
}]);

We also add a scope property named error. This property is populated any time the user fails to authenticate, displaying a “Login Failed” message. We will see how the error is presented later in the chapter. Once the user authenticates, we make a call to the AngularJS business logic service setCreds and pass the user’s username and password to be saved in a cookie. We then redirect the user to the main application link.

Security Modifications to Other Controllers

We must also make minor modifications to the other two controllers in our blog project. Open your editor and replace the two controllers added earlier with the code shown next. Notice we now inject the $location service and the checkCreds business service that we added back in Chapter 8. The checkCreds service works by checking the user’s credentials at the top of the controller. If the user has not authenticated, a call is made to the path method on the $location service to redirect the user to the login page (we will cover the new login path shortly):

/* chapter10/controllers.js excerpt */

blogControllers.controller('BlogCtrl', 
  ['$scope', 'BlogList', '$location', 'checkCreds',
    function BlogCtrl($scope, BlogList, $location, checkCreds) {
      if(!checkCreds()){
        $location.path('/login');
      }

      BlogList.get({},
        function success(response) {
          console.log("Success:" + JSON.stringify(response));
          $scope.blogList = response;
        },
        function error(errorResponse) {
          console.log("Error:" + JSON.stringify(errorResponse));
        }
      );
}]);


blogControllers.controller('BlogViewCtrl', 
  ['$scope', '$routeParams', 'BlogPost', '$location', 'checkCreds',
    function BlogViewCtrl($scope, $routeParams, BlogPost, 
      $location, checkCreds) {
      if(!checkCreds()){
        $location.path('/login');
      }
      var blogId = $routeParams.id;

      BlogPost.get({id: blogId},
        function success(response) {
          console.log("Success:" + JSON.stringify(response));
          $scope.blogEntry = response;
        },
        function error(errorResponse) {
          console.log("Error:" + JSON.stringify(errorResponse));
        }
      );
}]);

Adding a Logout Controller

We have one more change to make to the controllers.js file: we need to add a new controller to log the user out of the system and reset his credentials. Add the code shown here to the bottom of the controllers.js file. Once again, we make use of the AngularJS business logic services written back in Chapter 8 by adding a call to the deleteCreds service. The service call removes the user’s credentials, and then we redirect the user to the login page:

/* chapter10/controllers.js excerpt */

blogControllers.controller('LogoutCtrl', 
['$location', 'deleteCreds',
function LogoutCtrl($location, deleteCreds) {

  deleteCreds();
  $location.path('/login');

}]);

The entire controllers.js file is shown here to help make the changes clearer:

/* chapter10/controllers.js */

'use strict';
/* Controllers */

var blogControllers = 
  angular.module('blogControllers', []);
blogControllers.controller('BlogCtrl', 
  ['$scope', 'BlogList', '$location', 'checkCreds',
    function BlogCtrl($scope, BlogList, $location, checkCreds) {
      if(!checkCreds()){
        $location.path('/login');
      }

      BlogList.get({},
        function success(response) {
          console.log("Success:" + JSON.stringify(response));
          $scope.blogList = response;
        },
        function error(errorResponse) {
          console.log("Error:" + JSON.stringify(errorResponse));
        });
}]);

blogControllers.controller('BlogViewCtrl', 
  ['$scope', '$routeParams', 'BlogPost', '$location', 'checkCreds',
    function BlogViewCtrl($scope, $routeParams, BlogPost, 
      $location, checkCreds) {
        if(!checkCreds()){
          $location.path('/login');
        }
        var blogId = $routeParams.id;

        BlogPost.get({id: blogId},
          function success(response) {
            console.log("Success:" + JSON.stringify(response));
            $scope.blogEntry = response;
          },
          function error(errorResponse) {
            console.log("Error:" + JSON.stringify(errorResponse));
          });
}]);

blogControllers.controller('LoginCtrl', 
  ['$scope', '$location','Login', 'setCreds',
    function LoginCtrl($scope, $location, Login, setCreds) {

      $scope.submit = function(){
      $scope.sub = true;
      var postData = {
        "username" : $scope.username,
        "password" : $scope.password
      };

      Login.login({}, postData,
        function success(response) {
          console.log("Success:" + JSON.stringify(response));
          if(response.authenticated){
            setCreds($scope.username, $scope.password)
            $location.path('/');
          }else{
            $scope.error = "Login Failed"
          }
        },
        function error(errorResponse) {
          console.log("Error:" + JSON.stringify(errorResponse));
        });
      };
}]);

blogControllers.controller('LogoutCtrl', 
  ['$location', 'deleteCreds',
    function LogoutCtrl($location, deleteCreds) {
      deleteCreds();
      $location.path('/login');
}]);

Next, we will added a new login template and the associated CSS. We will then add two new paths to the $routeProvider section of the app.js file.

Adding a Login Template

Right-click the project node and add a new HTML file to the partials folder. Name the new file login.html. Replace the content of the newly created file with the code shown here. Notice that we use the ng-submit directive to connect the submit method in our LoginCtrl to the form for form submission:

<!-- chapter10/login.html -->

<div class="blog-login-wrapper">

<form class="" ng-submit="submit()" ng-controller="LoginCtrl">
<div class="blog-login-error">{{error}}</div>
<div class="blog-login-label">
<label for="username">Username:</label></div>
<div class="blog-login-element">
<input type="text" ng-model="username" name="username" 
  placeholder="username" required/></div>
<div class="blog-login-label">
<label for="password">Password:</label></div>
<div class="blog-login-element">
<input type="password" ng-model="password" name="password" 
  placeholder="password" required/></div>
<div class="blog-login-button">
<button type="submit" class="form-button">Sign in</button></div>
</form>

</div>

Now open the CSS file styles.css in your editor and add the following code to the bottom of the file. Notice that we use CSS3 media queries like @media screen and (min-width: 1200px) to make our login template be responsive and look good on any mobile or desktop platform:

/* chapter10/styles.css */

.blog-login-wrapper{
  float: left;
  background: #e0e0e0;
  border-radius:6px;
  -moz-border-radius:6px; /* Firefox 3.6 and earlier */
  border: darkgreen solid 1px;
}

@media screen and (min-width: 1200px){
  .blog-login-wrapper{
    width: 40%;
    margin: 10% 0 0 30%;
    padding: 1%;
    background: #e0e0e0;
    border-radius:6px;
    -moz-border-radius:6px; /* Firefox 3.6 and earlier */
    border: darkgreen solid 1px;
  }
}

@media screen and (max-width: 1200px){
  .blog-login-wrapper{
    width: 40%;
    margin: 10% 0 0 30%;
    padding: 1%;
    background: #e0e0e0;
    border-radius:6px;
    -moz-border-radius:6px; /* Firefox 3.6 and earlier */
    border: darkgreen solid 1px;
  }
}

@media screen and (max-width: 600px){
  .blog-login-wrapper{
    width: 80%;
    margin: 10% 0 0 10%;
    padding: 1%;
    background: #e0e0e0;
    border-radius:6px;
    -moz-border-radius:6px; /* Firefox 3.6 and earlier */
    border: darkgreen solid 1px;
  }
}

.blog-login-label{
  float: left;
  width: 70%;
  margin: 0 0 0 15%;
  padding: 1% 0 0 0;
  text-align: center;
}

.blog-login-element{
  float: left;
  width: 70%;
  margin: 0 0 0 15%;
  padding: 1% 0 0 0;
  text-align: center;
}

.blog-login-button{
  float: left;
  width: 100%;
  margin: 0 0 0 0;
  padding: 5% 0 0 0;
  text-align: center;
}

.blog-login-error{
  float: left;
  width: 100%;
  margin: 0 0 0 0;
  padding: 0 0 0 0;
  text-align: center;
  color: red;
}

Adding a Logout Link

Finally, we need to make one more change to our blog application: we need to modify the menu.html file and add the new “Logout” menu link. Here is the line you’ll need to add to the menu.html file. The new logout link maps to the logout route that we just added:

<!-- chapter10/menu.html excerpt -->

<li><a id="lo" href="#!logOut">Logout</a></li>

The complete menu.html file is shown here for convenience:

<!-- chapter10/menu.html complete file -->

<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<!-- Brand and toggle get grouped for better mobile display -->

<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" 
  data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>

<a class="navbar-brand" style="{{brandColor}}" href="#!/">{{label}}</a>
</div>

<!--Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">

<li class="{{aboutActiveClass}}"><a href="#!about">About</a></li>
<li class="">
<a href="https://github.com/KenWilliamson">Download Project Code</a>
</li>

<li><a id="lo" href="#!logOut">Logout</a></li>

</ul>

</div><!-- /.navbar-collapse -->
</div>

</nav>

Once you have made all the changes outlined in this chapter, your blog application should have all the needed security additions that were specified. To test the changes that were made, we will run the project and check for errors.

Running the Blog Application

Right-click the project node and select “Run” from the menu. Your project should run and you should see the screen in Figure 10-1. If you do not see the login screen, check that all the changes outlined in this chapter were performed correctly. Turn on developer tools for your browser and look for errors, as described in previous chapters.

Alt Text
Figure 10-1. The login screen

Logging In

Once your project is running, do the following:

  1. Enter “node” as the username.
  2. Enter “password” as the password.
  3. Click the “Sign in” button.

You should now see the same blog screens that you built in the previous chapters. The application should function just as before with no changes. Navigate through the application to validate that everything works correctly.

If you were to enter incorrect user credentials, you would see the error message described earlier (“Login Failed”) displayed in red. Notice the new menu item “Logout” at the right end of the menu bar. Click “Logout” and your session should end. You should then be taken back to the login screen. If the login and logout process work correctly, your security changes were implemented successfully.

Testing with Karma

We’ve added a new AngularJS service and two new controllers to our blog application. We now need to test the application to make certain there are no defects in our code. We also need to validate that all previous unit tests are still passing.

We will start off by writing a test specification for the new service. We will then write two new test specifications for the two new controllers. Once our unit testing is complete, we will make changes to our end-to-end testing.

Karma Configuration

We already have an up-to-date Karma configuration file for our blog project. There should be no changes to the file at this point. The complete karma.conf.js file is shown here for reference:

/* chapter10/karma.conf.js */

module.exports = function (config) {
    config.set({
        basePath: '../',
        files: [
            "public_html/js/libs/angular.min.js",
            "public_html/js/libs/angular-mocks.js",
            "public_html/js/libs/angular-route.min.js",
            "public_html/js/libs/angular-resource.min.js",
            "public_html/js/libs/angular-cookies.min.js",
            "public_html/js/*.js",
            "public_html/partials/*.html",
            "test/**/*Spec.js" 
        ],
        preprocessors: {
            'public_html/partials/*.html': ['ng-html2js']
        },
        exclude: [
        ],
        autoWatch: true,
        frameworks: [
            "jasmine"
        ],
        browsers: [
            "Chrome",
            "Firefox"
        ],
        plugins: [
            "karma-junit-reporter",
            "karma-chrome-launcher",
            "karma-firefox-launcher",
            "karma-jasmine",
            "karma-ng-html2js-preprocessor"
        ],        
        ngHtml2JsPreprocessor: {            
            stripPrefix: 'public_html/'
    });
};

Karma Test Specifications

We need to add unit test specifications for the new Login service and the two new controllers. The following code shows the new test specification for the Login service. The service relies on a REST service, so we will only test to make sure we can inject the service. We will actually test the service interaction with the REST service during end-to-end testing. If there are any issues, we will find them there. Add this test specification to the project’s servicesSpec.js file:

/* chapter10/servicesSpec.js excerpt */

describe('test Login', function () {       
  var $rootScope;
  var login;

  beforeEach(module('blogServices'));

  beforeEach(inject(function ($injector) {           
    $rootScope = $injector.get('$rootScope');
    login = $injector.get('Login');
  }));

  it('should test Login service', function () {            
    expect(login).toBeDefined();
  });
});

The complete servicesSpec.js file is shown here:

/* chapter10/servicesSpec.js complete file */

describe('AngularJS Blog Service Testing', function () { 
  describe('test BlogList', function () {       
    var $rootScope;
    var blogList;
    beforeEach(module('blogServices'));
    beforeEach(inject(function ($injector) {           
      $rootScope = $injector.get('$rootScope');
       blogList = $injector.get('BlogList');
    }));
    it('should test BlogList service', function () {            
      expect(blogList).toBeDefined();
    });
  });

  describe('test BlogPost', function () {       
    var $rootScope;
    var blogPost;
    beforeEach(module('blogServices'));
    beforeEach(inject(function ($injector) {           
      $rootScope = $injector.get('$rootScope');
      blogPost = $injector.get('BlogPost');
    }));
    it('should test BlogPost service', function () {            
      expect(blogPost).toBeDefined();
    });
  });
    
  describe('test Login', function () {       
    var $rootScope;
     var login;
     beforeEach(module('blogServices'));
     beforeEach(inject(function ($injector) {           
       $rootScope = $injector.get('$rootScope');
       login = $injector.get('Login');
     }));
     it('should test Login service', function () {            
       expect(login).toBeDefined();
     });
   });
});

Now we need test specifications for the two new controllers. First we show the test specification for the LoginCtrl controller. We first get a reference to the controller and then call the submit method attached to the scope. We use a scope property to validate that the method call was successful:

/* chapter10/controllerSpec.js excerpt */

    describe('LoginCtrl', function () {
        var scope, ctrl;

        beforeEach(inject(function ($rootScope, $controller) {
            scope = $rootScope.$new();
            ctrl = $controller('LoginCtrl', {$scope: scope});
            scope.submit();
        }));
        it('should show submit success', function () {
            console.log("LoginCtrl:" + scope.sub);
            expect(scope.sub).toEqual(true);            
        });
    });

Next is the test specification for the LogoutCtrl controller. In this case, we just validate that we can get a reference to the controller. We will validate that the controller actually handles logout correctly when we do end-to-end testing:

/* chapter10/controllerSpec.js excerpt */

describe('LogoutCtrl', function () {
        var scope, ctrl;

        beforeEach(inject(function ($rootScope, $controller) {
            scope = $rootScope.$new();
            ctrl = $controller('LogoutCtrl', {$scope: scope});
        }));

        it('should create LogoutCtrl controller', function () {
            console.log("LogoutCtrl:" + ctrl);
            expect(ctrl).toBeDefined();
            //expect(scope.blogList).toBeUndefined();
        });
    });

The complete controllerSpec.js file is shown next. Make the changes to your file in the blog application and validate that it matches the version shown here:

/* chapter10/controllerSpec.js complete file */

describe('AngularJS Blog Application', function () {
  beforeEach(module('blogApp'));
  //beforeEach(module('blogServices'));

  describe('BlogCtrl', function () {
    var scope, ctrl;
    beforeEach(inject(function ($rootScope, $controller) {
      scope = $rootScope.$new();
      ctrl = $controller('BlogCtrl', {$scope: scope});
    }));
    it('should create show blog entry count', function () {
      console.log("blogList:" + scope.blogList);
      expect(scope.blogList.length).toEqual(0);
      //expect(scope.blogList).toBeUndefined();
    });
  });

  describe('BlogViewCtrl', function () {
    var scope, ctrl, $httpBackend;
    beforeEach(inject(function (_$httpBackend_, $routeParams, 
      $rootScope, $controller) {
      $httpBackend =  _$httpBackend_;
      $httpBackend.expectGET('blogPost').respond({_id: '1'});
      $routeParams.id = '1';
      scope = $rootScope.$new();            
      ctrl = $controller('BlogViewCtrl', {$scope: scope});
    }));
    it('should show blog entry id', function () {            
      //expect(scope.blogEntry._id).toEqual(1);
      //expect(scope.blogList).toBeUndefined();
      expect(scope.blg).toEqual(1);
    });

  });    
    
  describe('LoginCtrl', function () {
    var scope, ctrl;
    beforeEach(inject(function ($rootScope, $controller) {
      scope = $rootScope.$new();
      ctrl = $controller('LoginCtrl', {$scope: scope});
      scope.submit();
    }));
    it('should show submit success', function () {
      console.log("LoginCtrl:" + scope.sub);
      expect(scope.sub).toEqual(true);
      //expect(scope.blogList).toBeUndefined();
    });
  });
    
  describe('LogoutCtrl', function () {
    var scope, ctrl;
    beforeEach(inject(function ($rootScope, $controller) {
      scope = $rootScope.$new();
      ctrl = $controller('LogoutCtrl', {$scope: scope});
    }));
    it('should create LogoutCtrl controller', function () {
      console.log("LogoutCtrl:" + ctrl);
      expect(ctrl).toBeDefined();
      //expect(scope.blogList).toBeUndefined();
    });
  });
});

End-to-End Testing

We will make several changes to the end-to-end test specifications for our blog application here. We will need to log into the blog application with the script. Then, once logged in, we will navigate through the blog as before to verify that all previous E2E functionality still works. We will then need to log out with the test script to test the logout functionality.

Protractor Configuration

We already created a Protractor configuration file for the blog application in Chapter 5. The Protractor configuration file is shown here for reference:

/* chapter10/conf.js Protractor configuration file */

exports.config = { 
  seleniumAddress: 'http://localhost:4444/wd/hub', 
  specs: ['e2e/blog-spec.js'] 
};

Protractor Test Specification

The blog-spec.js file shown here contains several changes. First notice that the script needs to complete the login form by populating the username and password fields. Then it looks up the login form button by the CSS class name, and clicks the button:

/* chapter10/blog-spec.js Protractor test specification */

describe("Blog Application Test", function(){
  it("should test the main blog page", function(){        
    browser.get("http://localhost:8383/AngularJsBlog/");
    //logs into the blog application
    element(by.model("username")).sendKeys("node");
    element(by.model("password")).sendKeys("password");
    element(by.css('.form-button')).click(); 
    expect(browser.getTitle()).toEqual("AngularJS Blog");        
    //gets the blog list
    var blogList = 
      element.all(by.repeater('blogPost in blogList'));        
    //tests the size of the blogList
    expect(blogList.count()).toEqual(1);       
        
    browser.get(
      "http://localhost:8383/AngularJsBlog/#!/
      blogPost/5394e59c4f50850000e6b7ea");
    expect(browser.getTitle()).toEqual("AngularJS Blog");
        
    //gets the comment list
    var commentList = 
      element.all(by.repeater('comment in blogEntry.comments'));        
      //checks the size of the commentList
      expect(commentList.count()).toEqual(2);        
      element(by.css('.navbar-brand')).click();        
      //logs out of the blog application
      element(by.id('lo')).click();        
      expect(browser.getTitle()).toEqual("AngularJS Blog");
    });
});

Once the script has successfully logged into the application, it navigates through the application as before. Then, at the end of the test script, it looks up the logout link by id. It then clicks the link, logging out of the application.

The end-to-end test specification validates that the login process works. It also validates all the previous functionality tested in Chapter 9. Then it validates that the logout process works correctly.

Protractor Testing

Now, with those changes added, we are ready to start the end-to-end testing.

Start a new command window and enter the following command to start the test server:

webdriver-manager start

Open a new command window and navigate to the root of the Chapter 10 project. Type the command:

protractor test/conf.js

You should see a browser window open. You should then see the test script log into the blog application and navigate through the pages of the application, and finally log out of the application. When the Protractor script has finished, the browser window will close.

You should see results like the following in the command window when the Protractor script completes. The number of seconds that it takes the script to finish will vary depending on your particular system:

Finished in 3.285 seconds
1 test, 5 assertions, 0 failures

One Last Point on Security

I want to emphasize one last thing about implementing security in a JavaScript application. Any security that you implement in JavaScript can be circumvented by the user, as I explained at the start of the chapter. The login screen and security that we implemented in this chapter are completely dependent on the login REST service.

The login screen is used just as a way to gather and store the user’s credentials in a safe place temporarily and to control the authentication process for each REST service that contains private data. The user’s credentials are removed after each session and have to be entered again at each login, unless the user chooses to save their credentials.

Conclusion

In the next chapter you will see how the user’s credentials are used to gain access to private REST services that add new blog posts and comments. You will first deploy the REST services and the AngularJS application together in a MEAN stack deployment to your local machine to see the whole process in action. Once the application is up and running on your local machine, you will be able to use the developer tools in Chrome to view the REST service logs at runtime: you’ll be able to view the URL, request, and response of each service call.

You will also see any errors that occur. Once you have tested the MEAN stack on your local machine, you will deploy the project to the cloud using Git, which is a distributed version control and source code management (SCM) system initially developed by Linus Torvalds.