The flow works like this:
- The server generates a CSRF Token, which it sends to the client (typically through a setting in the cookie or hidden on a form page).
- The client records this token and sends it back to the server via an HTTP header.
- The server reads the HTTP header, compares it to the known CSRF Token for that session, then allows the request to go through if it matches.
A typical problem is that the backend's name for the cookie setting differs from what AngularJS is looking for, or the header that AngularJS sends back to the backend is not what it expects (or both).
By default, AngularJS looks for a cookie named "XSRF-TOKEN" and sets the header as "X-XSRF-TOKEN" as described here. Fortunately, it's easy to change this on the client side. I typically use a Django backend, and that sends a cookie named "csrftoken" to the browser. The 403 Forbidden error is due to the mismatch.
If using ng-boilerplate (highly recommended):
- Open up app.js.
- Inject $http and $cookies into the run method.
- Read the incoming CSRF token from the cookie or form ('csrftoken' or equivalent).
- Send it to the server during each request using the header that the backend is expecting (see the docs for your backend).
angular.module( '[your module name]',
... [some dependencies] ...
'ngCookies',
... [other dependencies] ...
)
...
.run( function run( titleService, $http, $cookies ){
titleService.setSuffix( '[title]' );
// For CSRF token compatibility with Django
$http.defaults.headers.post['X-CSRFToken'] = $cookies['csrftoken'];
})
...
;
Don't forget to add "'vendor/angular/angular-cookies.js'," under vendor_files in build.config.js for the build to pick up. Note that you will have to download the AngularJS files to get angular-cookies.js, since it currently does not come with the standard ng-boilerplate install. If you're not using ng-boilerplate, just be sure to add a reference to that JS file in your HTML.
This code will also work when using $resource, since that uses $http underneath. Preferably, we should set this during runtime in $resource itself, but this is not yet supported in the current stable AngularJS release (1.0.7), and I don't want to switch to an unstable version just to get this working.
I prefer this method than doing the modification on the server end, since when using multiple JS libraries, the header would need to be changed/added on the client end anyway. I also prefer transferring the token via the cookie instead of a form, since the latter could affect caching.
Thanks to this StackOverflow post, which provided the main clues on how to solve this in Angular.