“WARNING: Can't verify CSRF token authenticity

2019-06-22 05:38发布

问题:

I have a single page app that authenticates to another domain using CORS. All the requests are JSON requests.

My app can authenticates OK and can make GET requests OK. Authentication is using token_authenticatable. I.e. all requests append '?auth_token=whatever'

So, my actual problem is that when I try to do a PUT request I get a WARNING: Can't verify CSRF token authenticity message in the rails log as well as a CanCan::AccessDenied (You are not authorized to access this page.) exception.

Simply adding skip_before_filter :verify_authenticity_token to the rails controller fixes the issue.

Therefore I can only conclude that my ajax requests are sending an invalid or empty csrf_token.

I don't really understand how that can be, since I believe I am correctly sending the X-CSRF-Token header correctly with each ajax request.

Basically, my app authenticates and Devise sends back an auth_token and a csrf_token:

render :status => 200, :json => {
  :auth_token => @user.authentication_token,
  :csrf_token => form_authenticity_token
}

I then store those tokens in my ajax app, and using ajaxSend in jQuery, set it up so jQuery passes those tokens with each request:

initialize: ->
  @bindTo $(document), 'ajaxSend', @appendTokensToRequest

appendTokensToRequest: (event, jqXHR, options) ->
  if not @authToken? then return

  if @csrfToken?
    jqXHR.setRequestHeader 'X-CSRF-Token', @csrfToken

  if options.type is 'POST'
    options.data = options.data + (if options.data.match(/\=/) then '&' else '') +
    $.param auth_token:@authToken
  else
    options.url = options.url + (if options.url.match(/\?/) then '&' else '?') +
    $.param auth_token:@authToken

I can then see in the chrome network tab, that for each GET request the auth_token param is being sent, as well as the X-CSRF-Token header.

On PUT requests however it doesn't seem to be working though.

My theory is that CORS is stuffing things up. If you make a CORS request, your browser actually makes an additional OPTIONS request first just to check that you do have permission to access this resource.

I suspect that it is the OPTIONS request which is not passing the X-CSRF-Token header, thus rails immediately invalidates the csrf_token on the rails end. Then when jQuery makes the actual PUT request the csrf_token it passes is no longer valid.

Could this be the problem?

What can I do to prove that? Chrome doesn't seem to show me the OPTIONS requests in the network tab to help me debug the issue.

It's not a major issue, because I can just turn the CSRF stuff off. But I'd like to know why it's not working.

回答1:

I think you'll need to handle the OPTIONS request, which should respond with the various headers that will allow the CORS request, IIRC they are the access-control-allow-method, access-control-allow-origin and access-control-allow-headers. Because the OPTIONS request is failing, the PUT request probably isn't occurring.



回答2:

I just ran into the same issue. The problem is that the _session_id cookie cannot be sent in CORS. As a result, when Rails tries to verify the token, the session[:_csrf_token] is null and Rails generates a new one before comparison.

To solve the issue, you need to enable cookie sending in CORS. Here is the Mozilla Developer Network reference. Work is needed on both the server and client side to make it work.

Client - Refer to your client technologies document.

Server - Set the header Access-Control-Allow-Credentials to true (string) in the response to the preflight (HTTP OPTIONS) call.



回答3:

In Rails every form submission need CSRF token authenticity. It use to submit form securely. The CSRF token(each time) will create newly in rails when we open our Application. If the CSRF token not passing inside our controller this WARNING will show. We need to pass this token in all form submissions.