Security

With any client-side app, it’s always a good idea to think about security at build time. Additionally, it’s relatively tough to deliver 100% protection in any situation, and even more difficult to do it when the client can see the entire code.

In this chapter, we’re going to take a look at some techniques for keeping our application secure. We’ll look at how to master the $sce service to secure our text input through wrapping authorized requests with tokens (when talking to a protected back end).

Strict Contextual Escaping: the $sce Service

The strict contextual escaping mode (available by default in Angular version 1.2 and higher) tells our app that it requires bindings in certain contexts to result in a value that is marked as safe for use inside the context.

For instance, when we want to bind raw HTML to an element using ng-bind-html, we want Angular to render the element with HTML, rather than escaped text.

<textarea ng-model="htmlBody"></textarea>
<div ng-bind-html="{{htmlBody}}"></div>

$sce is a fantastic service that allows us to write whitelisted, secure code by default and goes a long way toward helping us prevent XSS and other vulnerabilities. Given this power, it’s important to understand what it is that we’re doing so we can use it wisely.

In the above example, the <textarea> is bound to the htmlBody model. In this textarea, the user can input whatever arbitrary code they would like to see rendered in the div. For instance, it might be a live preview for writing a blog post or comments, etc.

If the user can input any arbitrary text into the text field, we have essentially opened ourselves up to a giant security hole.

The $sce service does that for us, by default, on all interpolated expressions. No constant literals are ever untrusted.

At the root of embedded directives starting in version 1.2 and on, $scope values are not bound directly to the value of the binding, but to the result of the $sce.getTrusted() method.

Directives use the new $sce.parseAs() method instead of the $parse service to watch attribute bindings. The $sce.parseAs() method calls $sce.getTrusted() on all non-constant literals.

In effect, the ng-bind-html directive calls $sce.parseAsHtml() behind the scenes and binds the value to the DOM element. The ng-include directive runs this same behavior and any templateUrl defined on a directive.

When enabled, all built-in directives call out to $sce automatically. We can use this same behavior in our own directives and other custom components.

To set up $sce protection, we need to inject the $sce service.

angular.module('myApp', [])
  .directive('myDirective', ['$sce', 
    function($sce) {
      // We have access to the $sce service
  }])
  .controller('MyController', [
    '$scope', '$sce', 
    function($scope, $sce) {
      // We have access to the $sce service  
  }]);

Inside of our directive and our controller, we want to give Angular the ability to both allow trusted content back into the view and take trusted interpolated input.

The $sce service has a simple API that gives us the ability to both set and get trusted content of explicitly specific types.

For instance, let’s build an email previewer. This email client will allow users to write HTML in their email; we want to give them a live preview of their text.

The HTML we can use might look something like:

<div ng-app="myApp">
  <div ng-controller="MyController">
    <textarea ng-model="email.rawHtml"></textarea>
    <pre ng-bind-html="email.htmlBody"></pre>

Now, notice that we are taking a body of text in a <textarea></textarea> on a different property of email: email.rawHtml vs. email.htmlBody. Inside of our controller, we parse this email.rawHtml as HTML and output it to the browser.

Inside our controller, we can set up a $watch to monitor changes on the email.rawHtml and run a trusted parser on the HTML content any time it changes.

.controller('MyController', [
  '$scope', '$sce',
    function($scope, $sce) {
      // set up a watch on the email.rawHtml
      $scope.$watch('email.rawHtml', function(v) {
        // so long as we are not in the 
        // $compile phase
        if (v) {
          // Render the htmlBody as trusted HTML
          $scope.email.htmlBody =
            $sce.trustAsHtml($scope.email.rawHtml);
        }
      })
    }]);

Now, whenever the content of email.rawHtml changes, we’ll run a parser on the content and get back suitable HTML contents. Note that the content will be rendered as sanitized HTML that’s safe to source in the application.

Now, what if we want to support the user to write custom JavaScript to execute on the page? For instance, if we want to enable the user to write an ecard that includes custom JavaScript, we’ll want to enable specify they can run this custom JavaScript on the page.

The HTML invocation for this might look like:

  <textarea ng-model="email.rawJs"></textarea>
  <pre ng-bind="email.jsBody"></pre>
  <button ng-click="runJs()">Run</button>

With this snippet, we’re running the same mechanism for parsing our raw text into safe text. This time, we also add a third element, a button that calls runJs() on our scope.

As we saw with our HTML bindings, we’ll watch the JavaScript snippet:

.controller('MyController', [
  '$scope', '$sce',
    function($scope, $sce) {
      // set up a watch on email.rawJs
      $scope.$watch('email.rawJs', function(v) {
        if (v) {
          $scope.email.jsBody =
            $sce.trustAsJs($scope.email.rawJs);
        }
      });
    }]);

Notice that this time we did not use trustAsHtml(), but used the trustAsJs() method. This method tells Angular to parse the text as executable JavaScript code. At the end of this call, we’ll have a safe, parsed JavaScript snippet we can eval() in the context of the application.

We can now enable the runJs() method to be set by the user and run the JavaScript snippet supplied by email.rawJs.

// ...
$scope.runJs = function() {
  eval($scope.email.jsBody.toString());
}

There are more intelligent methods of running eval on JavaScript snippets. For production use, we recommend against using eval.

We get built-in protection in Angular: It will only load templates from the same domain and protocol as those within which the app is loaded. Angular enforces this protection by calling the $sce.getTrustedResourceUrl on the templateUrl.

This protocol does not replace the browser’s Same Origin policies and Cross-Origin Resource Sharing, or CORS. These policies will still be in effect to protect the browser.

We can override this value by whitelisting or blacklisting domains with the $sceDelegateProvider.

Whitelisting URLs

In the module’s config() function, we can set new whitelist and blacklists.

 
This page is a preview of ng-book.
Get the rest of this chapter plus 600 pages of the best Angular content on the web.

 

Ready to master AngularJS?

  • What if you could master the entire framework – with solid foundations – in less time without beating your head against a wall? Imagine how quickly you could work if you knew the best practices and the best tools?
  • Stop wasting your time searching and have everything you need to be productive in one, well-organized place, with complete examples to get your project up without needing to resort to endless hours of research.
  • You will learn what you need to know to work professionally with ng-book: The Complete Book on AngularJS or get your money back.
Get it now