Understanding Security in AngularJS Applications

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 post, 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.

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.

$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. In order to protect ourselves against malicious users, it’s a good idea to run our unsafe text through a sanitizer.

The $sce service does that for us, by default, on all interpolated expressions. No constant literals are
ever untrusted. For instance, this value is always a trusted one, as the value is a string.

Basically, 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.

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:

Now, notice that we are taking a body of text in a 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.

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:

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:

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.

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.

Configuring $sce

If we want to completely disable the sce subsystem from running our app (although we discourage this move, as it provides security by default), we can disable it in the config() function of our app like so:

 

Top