Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with:

Preventing Cross-Site Request Forgery (CSRF / XSRF) With AngularJS And ColdFusion

By Ben Nadel on

In a Cross-Site Request Forgery (CSRF or XSRF) attack, a malicious site gets an unsuspecting user to make a secret HTTP request back to a legitimate site, forcing an unintentional action. To prevent such attacks, you need to verify that an incoming HTTP request came from an authenticated user under normal circumstances. And, as luck would have it, AngularJS has some anti-CSRF functionality built right into the core of its HTTP / AJAX framework.


 
 
 

 
  
 
 
 

View this code in my project on GitHub.

In a Cross-Site Request Forgery attack, a malicious site gets a user to make an HTTP request back to the target site. However, because this HTTP request is being initiated from a foreign domain (ie, the malicious domain), the browser enforces some security boundaries that really help us. Specifically, the foreign domain does not have programmatic access to the cookies created by the target domain.

This restriction means that the malicious domain will not be able to echo cookie values in any sort of programmatic way, such as injecting them as an HTTP header. And, it's this limitation that AngularJS uses in its anti-CSRF feature.

When you make requests using the $http service (or anything built on top of $http, like $resource), AngularJS will look for the existence of the "XSRF-TOKEN" cookie. And, if it finds it, it will append the cookie value as the outgoing HTTP Header, "X-XSRF-TOKEN." This means that when you set the XSRF token cookie, AngularJS will send two tokens through which each HTTP AJAX request:

  • The cookie, XSRF-TOKEN.
  • The header, X-XSRF-TOKEN.

Our server-side ColdFusion code can then look to confirm that each incoming HTTP AJAX request contains both the cookie value and the header value; and, that they are the same value. If either one of these values is missing - or, if they do not match - the server should assume that the request came from a malicious source and should block the request.

To demonstrate this feature, I've created a small AngularJS and ColdFusion application. The main page - index.cfm - reads in the user's session tokens and then uses them to generate a user-specific XSRF token cookie. The API end-point then checks the incoming request data and verifies that the cookie token and the header token match.

The full code is in the GitHub repo; but, here is the index page. Notice that the XSRF-TOKEN cookie is generated at the top:

index.cfm - Our Main Page

  • <cfscript>
  •  
  • // Set the XSRF token cookie. The cookie is user-specific and
  • // "salted". Once in place, AngularJS will look for this cookie
  • // before initiating the $http / $resource requests; when found,
  • // AngularJS will automatically echo the token in the HEADER
  • // value, "X-XSRF-TOKEN".
  • cookie[ "XSRF-TOKEN" ] = lcase(
  • hash( "#session.cfid#-#session.cftoken#-#getTickCount()#" )
  • );
  •  
  • </cfscript>
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  • <cfcontent type="text/html; charset=utf-8" />
  •  
  • <!doctype html>
  • <html ng-app="demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Preventing Cross-Site Request Forgery With AngularJS And ColdFusion
  • </title>
  •  
  • <style type="text/css">
  •  
  • a[ ng-click ] {
  • color: blue ;
  • cursor: pointer ;
  • text-decoration: underline ;
  • }
  •  
  • </style>
  • </head>
  • <body ng-controller="DemoController">
  •  
  • <h1>
  • Preventing Cross-Site Request Forgery With AngularJS And ColdFusion
  • </h1>
  •  
  • <p>
  • <a ng-click="makeGetRequest()">Make GET Request</a>
  • &mdash;
  • <a ng-click="makePostRequest()">Make POST Request</a>
  • &mdash;
  • <a href="delete_cookie.cfm" target="_blank">Delete XSRF Cookie</a>
  • </p>
  •  
  • <h3>
  • API Log
  • </h3>
  •  
  • <ul>
  • <li ng-repeat="item in apiLog">
  • {{ item.message }}
  • </li>
  • </ul>
  •  
  •  
  • <script type="text/javascript" src="./assets/angularjs/angular.min.js"></script>
  • <script type="text/javascript" src="./assets/angularjs/angular-resource.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Define our AngularJS application.
  • var app = angular.module( "demo", [ "ngResource" ] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I am the application controller.
  • app.controller(
  • "DemoController",
  • function( $scope, $resource ) {
  •  
  • // I keep track of the API request values.
  • $scope.apiLog = [];
  •  
  • // I create a proxy for the server-side API end-point.
  • // I sit on top of the $http resource and will append
  • // an "X-XSRF-TOKEN" header to all outgoing requests
  • // if the "XSRF-TOKEN" cookie value is available.
  • var resource = $resource(
  • "api.cfm",
  • {},
  • {
  • performGet: {
  • method: "GET"
  • },
  • performPost: {
  • method: "POST"
  • }
  • }
  • );
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I make GET requests to the API.
  • $scope.makeGetRequest = function() {
  •  
  • resource.performGet()
  • .$promise
  • .then( handleResolution, handleRejection )
  • ;
  •  
  • };
  •  
  •  
  • // I make POST requests to the API.
  • $scope.makePostRequest = function() {
  •  
  • resource.performPost()
  • .$promise
  • .then( handleResolution, handleRejection )
  • ;
  •  
  • };
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I handle successful resource resolutions.
  • function handleResolution( response ) {
  •  
  • $scope.apiLog.unshift({
  • message: ( response.method + ": " + response.time )
  • });
  •  
  • }
  •  
  •  
  • // I handle resource response rejections.
  • function handleRejection( response ) {
  •  
  • $scope.apiLog.unshift({
  • message: "*** XSRF Attack! ***"
  • });
  •  
  • }
  •  
  • }
  • );
  •  
  •  
  • </script>
  •  
  • </body>
  • </html>

In this main page, there's really not a whole lot to see since AngularJS consumes the XSRF-TOKEN in a seamless way. You only start to get a sense of how the security works if you delete or expire the XSRF cookie (which you can see in the video at the top of this blog post).

The API page then checks to see if both the cookie and the header values match:

api.cfm - Our Mock API Page

  • <cfscript>
  •  
  • // Set up the default API response.
  • apiResponse = {
  • "statusCode" = 200,
  • "statusText" = "OK",
  • "contentType" = "application/x-json",
  • "data" = {}
  • };
  •  
  • // When processing the API request, we are going to check to see
  • // if the XSRF cookie and header values match. If either of these
  • // values is missing, or they do not match, we'll raise an
  • // exception. This error-oriented routing simplifies the logic.
  • try {
  •  
  • // The value WE set.
  • xsrfCookie = cookie[ "XSRF-TOKEN" ];
  •  
  • // The value ANGULARJS set.
  • xsrfToken = getHttpRequestData().headers[ "X-XSRF-TOKEN" ];
  •  
  • if ( xsrfCookie != xsrfToken ) {
  •  
  • throw( type = "XsrfTokenMismatch" );
  •  
  • }
  •  
  • apiResponse.data[ "method" ] = cgi.request_method;
  • apiResponse.data[ "time" ] = getTickCount();
  •  
  • } catch ( any error ) {
  •  
  • // If we made it this far, some part of the XSRF validation
  • // failed. Either one of the tokens was missing; or, the two
  • // tokens did not match. In any case, the request is not valid.
  •  
  • apiResponse.statusCode = 401;
  • apiResponse.statusText = "Unauthorized";
  • apiResponse.data = {};
  •  
  • }
  •  
  • </cfscript>
  •  
  • <cfheader
  • statuscode="#apiResponse.statusCode#"
  • statustext="#apiResponse.statusText#"
  • />
  •  
  • <!--- Serialize the API response. --->
  • <cfcontent
  • type="#apiResponse.contentType#; charset=utf-8"
  • variable="#charsetDecode( serializeJson( apiResponse.data ), 'utf-8' )#"
  • />

As you can see, the ColdFusion code checks to see if the incoming XSRF-TOKEN cookie matches the incoming X-XSRF-TOKEN header. And, if they do not match - or if one of them is missing - the ColdFusion code raises an exception which gets translated into a "401 Unauthorized" response.

It's pretty awesome that AngularJS built this kind of Cross-Site Request Forgery protection right into the core of the framework; but, of course, this only helps you when you make a request using the AngularJS $http service (or one of its derivatives). For non-AJAX requests, you may want to checkout the CSRF security enhancements added in ColdFusion 10.




Reader Comments

Thanks man,
I love how I never have to search for too long... ..in this case my search was about 10 seconds on google to find yet another useful post from you. lol... You guys rock! :)

There is a jQuery plugin for this as well. Aptly named, jQuery-cookie is something I have used to validate in php.

jQuery-cookie:
http://plugins.jquery.com/cookie/

What about x-header tokens?

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.