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:
CSRF not designed for GET or HEAD requests. XSRF-TOKEN name because angular expects this name. This is the default if you want another name google how to change the angular default and you will find. Angular default again it expects the cookie to be at the root of the domain. (Guessing)
IT MUST BE A RELATIVE PATH Httponly false because if true, angular or any other JavaScript won't be able to access it. CSRF not designed for GET or HEAD requests. XSRF-TOKEN name because angular expects this name. This is the default if you want another name google how to change the angular default and you will find.
XSRF-TOKEN name because angular expects this name. This is the default if you want another name google how to change the angular default and you will find. Angular default again it expects the cookie to be at the root of the domain. (Guessing) And no absolute paths because cookies are only sent for the same domain. Cookies are domain binded.
For every POST request I want my client to read the XSRF-token and set a X-XSRF-TOKEN header to this token. I'll check every request by checking if the request header and the user session XSRF-token match. If they do, I'll also check JWT for authentication if I need. After validating the XSRF-token, I'll make changes to the database.
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');
}]);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With