Rails CSRF Protection + Angular.js: protect_from_forgery makes me to log out on POST
If the protect_from_forgery
option is mentioned in application_controller, then I can log in and perform any GET requests, but on very first POST request Rails resets the session, which logs me out.
I turned the protect_from_forgery
option off temporarily, but would like to use it with Angular.js. Is there some way to do that?
I think reading CSRF-value from DOM is not a good solution, it's just a workaround.
Here is a document form angularJS official website http://docs.angularjs.org/api/ng.$http :
Since only JavaScript that runs on your domain could read the cookie, your server can be assured that the XHR came from JavaScript running on your domain.
To take advantage of this (CSRF Protection), your server needs to set a token in a JavaScript readable session cookie called XSRF-TOKEN on first HTTP GET request. On subsequent non-GET requests the server can verify that the cookie matches X-XSRF-TOKEN HTTP header
Here is my solution based on those instructions:
First, set the cookie:
# app/controllers/application_controller.rb
# Turn on request forgery protection
protect_from_forgery
after_action :set_csrf_cookie
def set_csrf_cookie
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end
Then, we should verify the token on every non-GET request.
Since Rails has already built with the similar method, we can just simply override it to append our logic:
# app/controllers/application_controller.rb
protected
# In Rails 4.2 and above
def verified_request?
super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
end
# In Rails 4.1 and below
def verified_request?
super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
end
If you're using the default Rails CSRF protection (<%= csrf_meta_tags %>
), you can configure your Angular module like this:
myAngularApp.config ["$httpProvider", ($httpProvider) ->
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
]
Or, if you're not using CoffeeScript (what!?):
myAngularApp.config([
"$httpProvider", function($httpProvider) {
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
}
]);
If you prefer, you can send the header only on non-GET requests with something like the following:
myAngularApp.config ["$httpProvider", ($httpProvider) ->
csrfToken = $('meta[name=csrf-token]').attr('content')
$httpProvider.defaults.headers.post['X-CSRF-Token'] = csrfToken
$httpProvider.defaults.headers.put['X-CSRF-Token'] = csrfToken
$httpProvider.defaults.headers.patch['X-CSRF-Token'] = csrfToken
$httpProvider.defaults.headers.delete['X-CSRF-Token'] = csrfToken
]
Also, be sure to check out HungYuHei's answer, which covers all the bases on the server rather than the client.