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.
The angular_rails_csrf gem automatically adds support for the pattern described in HungYuHei's answer to all your controllers:
# Gemfile
gem 'angular_rails_csrf'
The answer that merges all previous answers and it relies that you are using Devise
authentication gem.
First of all, add the gem:
gem 'angular_rails_csrf'
Next, add rescue_from
block into application_controller.rb:
protect_from_forgery with: :exception
rescue_from ActionController::InvalidAuthenticityToken do |exception|
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
render text: 'Invalid authenticity token', status: :unprocessable_entity
end
And the finally, add the interceptor module to you angular app.
# coffee script
app.factory 'csrfInterceptor', ['$q', '$injector', ($q, $injector) ->
responseError: (rejection) ->
if rejection.status == 422 && rejection.data == 'Invalid authenticity token'
deferred = $q.defer()
successCallback = (resp) ->
deferred.resolve(resp)
errorCallback = (resp) ->
deferred.reject(resp)
$http = $http || $injector.get('$http')
$http(rejection.config).then(successCallback, errorCallback)
return deferred.promise
$q.reject(rejection)
]
app.config ($httpProvider) ->
$httpProvider.interceptors.unshift('csrfInterceptor')
I saw the other answers and thought they were great and well thought out. I got my rails app working though with what I thought was a simpler solution so I thought I'd share. My rails app came with this defaulted in it,
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
I read the comments and it seemed like that is what I want to use angular and avoid the csrf error. I changed it to this,
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
end
And now it works! I don't see any reason why this shouldn't work, but I'd love to hear some insight from other posters.
I've used the content from HungYuHei's answer in my application. I found that I was dealing with a few additional issues however, some because of my use of Devise for authentication, and some because of the default that I got with my application:
protect_from_forgery with: :exception
I note the related stack overflow question and the answers there, and I wrote a much more verbose blog post that summarises the various considerations. The portions of that solution that are relevant here are, in the application controller:
protect_from_forgery with: :exception
after_filter :set_csrf_cookie_for_ng
def set_csrf_cookie_for_ng
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end
rescue_from ActionController::InvalidAuthenticityToken do |exception|
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
render :error => 'Invalid authenticity token', {:status => :unprocessable_entity}
end
protected
def verified_request?
super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
end
I found a very quick hack to this. All I had to do is the following:
a. In my view, I initialize a $scope
variable which contains the token, let's say before the form, or even better at controller initialization:
<div ng-controller="MyCtrl" ng-init="authenticity_token = '<%= form_authenticity_token %>'">
b. In my AngularJS controller, before saving my new entry, I add the token to the hash:
$scope.addEntry = ->
$scope.newEntry.authenticity_token = $scope.authenticity_token
entry = Entry.save($scope.newEntry)
$scope.entries.push(entry)
$scope.newEntry = {}
Nothing more needs to be done.
angular
.module('corsInterceptor', ['ngCookies'])
.factory(
'corsInterceptor',
function ($cookies) {
return {
request: function(config) {
config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
return config;
}
};
}
);
It's working on angularjs side!
来源:https://stackoverflow.com/questions/14734243/rails-csrf-protection-angular-js-protect-from-forgery-makes-me-to-log-out-on