AngularJS is not sending-X_XSRF-TOKEN

Problem

I am developing an Ionic mobile app to talk to my backend Rails server using a JSON API. I have read that AngularJS will automatically handle XSRF protection by sending a header X-XSRF-TOKEN on the POST request if the first GET request returns a cookie named XSRF-TOKEN

I have updated my Rails application_controller.rb to be as follows:

class ApplicationController < ActionController::Base
  protect_from_forgery
  after_filter :set_access_control_headers
  after_filter :set_csrf_cookie_for_ng

  def after_sign_in_path_for(resource)
    main_path
  end

  def after_sign_out_path_for(resource)
    login_path
  end

  ##
   # Sets headers to support AJAX Cross-Origin Resource Sharing.
   # This is only needed for testing within browser (i.e. mobile apps do not need it).
   ##
  def set_access_control_headers
    # hosts who can make AJAX requests
    headers['Access-Control-Allow-Origin'] = 'http://localhost:8100'
    headers['Access-Control-Request-Method'] = '*'
    headers['Access-Control-Allow-Headers'] = 'accept, content-type, x-xsrf-token'
    # allow clients to use cookies to track session state
    headers['Access-Control-Allow-Credentials'] = 'true'
  end

  ##
   # Sets a cookie containing an XSRF token. This should be returned by the
   # client as a header field named 'X-XSRF-TOKEN'
  def set_csrf_cookie_for_ng
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  end

protected

  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end
end

The AngularJS code is:

$http({
  method: 'POST',
  url: $scope.getBackendUrl() + '/reports.json',
  params: params,
  withCredentials: true,
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})

I have taken Wireshark dumps and can see that the Rails server is sending through the Cookie (and I can read this within AngularJS). However, AngularJS is not sending the header X-XSRF-TOKEN to my backend Rails server, causing an WARNING: Can't verify CSRF token authenticity.

I have read through a bunch of SO questions to no avail. E.g. XSRF headers not being set in AngularJS

I have added the CORS header stuff so that I could test from Chrome. Again, I can see the cookie come through from the server, but the header is not being sent back. However, the cookie is being sent back in the 'Cookie' header field.

Can anyone see what I am missing, or things I can try in order to solve this? I am currently parsing the Cookie header field in the request to pull out the token and check authenticity to get around the issue while testing.

def verified_request?
  # should just need to do the below, but for some reason AngularJS is not setting 'X-XSRF-TOKEN'
  #super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  if(super)
    true
  else
    cookie = request.headers['Cookie'] || ""
    value = cookie.nil? ? "" : CGI.unescape( cookie.gsub(/.*XSRF-TOKEN=(.+);.*/, '\1') )
    form_authenticity_token == value
  end
end

Versions:

  • Rails 3.2.3
  • Ionic 1.1.6 (which bundles in AngularJS)
Problem courtesy of: Al J

Solution

According to the documentation

$http: The header will not be set for cross-domain requests.

If you have to use CORS, you would also doing cross domain requests. Would still be able to do it yourself with an $httpInterceptor. You first read the cookie value, and then attach the header to the config before the request is fired.

function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
}
module.factory('XSRFInterceptor', function() {
    var XSRFInterceptor = {
        request: function(config) {
            var token = readCookie('XSRF-TOKEN');
            if (token) {
                config.headers['X-XSRF-TOKEN'] = token;
            }
            return config;
        }
    };
    return XSRFInterceptor;
}]);
module.config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('XSRFInterceptor');
}]);
Solution courtesy of: Jeremy Wilken

Discussion

Just for anyone who came here like me trying to figure out why Angular is not sending X-XSRF-TOKEN header:

In my case I did not pay attention that I'm sending XSRF-TOKEN cookie with HttpOnly flag.

Discussion courtesy of: Eugene

This recipe can be found in it's original form on Stack Over Flow.