ActionController::InvalidAuthenticityToken in RegistrationsController#create

前端 未结 8 1374
后悔当初
后悔当初 2020-11-29 19:39

Hi I am using Devise for my user authentication suddenly my new user registration was not working.

this was error I am getting.

ActionController::In         


        
相关标签:
8条回答
  • 2020-11-29 20:14

    TLDR: You are probably seeing this issue because your form submits via XHR.

    Few things first:

    1. Rails includes a CSRF token inside the head tag of your page.
    2. Rails evaluates this CSRF token anytime you perform a POST, PATCH or DELETE request.
    3. This token expires when you sign in or sign out

    A bog standard HTTP sign-in will cause a full page refresh, and the old CSRF token will be flushed and replaced with the brand new one that Rails creates when you sign in.

    An AJAX sign in will not refresh the page, so the crusty old, stale CSRF token, which is now invalid, is still present on your page.

    The solution is to update the CSRF token inside your HEAD tag manually after AJAX sign in.


    Some steps that I have shamelessly borrowed from a helpful thread on this matter.

    Step 1: Add the new CSRF-token to the response headers which are sent after a successful sign in

    class SessionsController < Devise::SessionsController
    
      after_action :set_csrf_headers, only: :create
    
      # ...
    
      protected
        def set_csrf_headers
          if request.xhr?
            # Add the newly created csrf token to the page headers
            # These values are sent on 1 request only
            response.headers['X-CSRF-Token'] = "#{form_authenticity_token}"
            response.headers['X-CSRF-Param'] = "#{request_forgery_protection_token}"
          end
        end
      end
    

    Step2: Use jQuery to update the page with the new values when the ajaxComplete event fires:

    $(document).on("ajaxComplete", function(event, xhr, settings) {
      var csrf_param = xhr.getResponseHeader('X-CSRF-Param');
      var csrf_token = xhr.getResponseHeader('X-CSRF-Token');
    
      if (csrf_param) {
        $('meta[name="csrf-param"]').attr('content', csrf_param);
      }
      if (csrf_token) {
        $('meta[name="csrf-token"]').attr('content', csrf_token);
      }
    });
    

    That's it. YMMV depending on your Devise configuration. I suspect though that this issue is ultimately caused by the fact that the old CSRF token is killing the request, and rails throws an exception.

    0 讨论(0)
  • 2020-11-29 20:17

    You have forgot to add <%= csrf_meta_tags %> in side your layout file.

    e.g.:

    <!DOCTYPE html>
    <html>
    <head>
    <title>Sample</title>
    <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
    </head>
    <body>
    
    <%= yield %>
    
    </body>
    </html>
    
    0 讨论(0)
  • 2020-11-29 20:19

    Browser Caching HTML Issue (2020)

    If you've tried all the remedies on this page and you're still having an issue with InvalidAuthenticityToken exceptions, it may be related to the browser caching HTML. There's an issue on Github with 100s of comments along with some reproducible code. In a nutshell, here's what was happening to me as it relates to HTML caching:

    1. User browses to website. Rails sets a signed session cookie on the first GET request. See config/initializers/session_store.rb for config options. This session cookie stores useful information, including a CSRF token that is used to decrypt and validate the authenticity of the request. Important: By default, the session cookie will expire when the browser window closes.
    2. User browses to a page containing a form. For me, I was receiving the most exceptions on my login page.
    3. Rails embeds a hidden CSRF token in this form, and submits this token along with the form data. Important: This token is embedded in the HTML.
    4. ActionController grabs the CSRF token from the params object and validates it with the CSRF token from the cookie using the verified_request? method in Rails 4.2+.

    Many browsers are now implementing HTML caching, so that when you open a page the HTML is loaded without a request. Unfortunately, when the browser is closed the session cookie is destroyed, so if the user closes the browser while on a form (such as a login page), then the first request will not contain a CSRF token thus throwing an InvalidAuthenticityError.

    Two common solutions

    1. Extend the expiry of your session cookie beyond the browser window.
    2. Detect in the browser if the session cookie is missing (via a proxy cookie), and if it is missing refresh the page.

    1. Extending the session cookie expiry

    As noted in this Github comment, Django takes this approach:

    Django puts adds the token in its own cookie called CSRF_COOKIE. This is a persistent cookie that expires in a year. If subsequent requests are made, the cookie's expiry is updated.

    In Rails:

    # config/initializers/session_store.rb 
    Rails.application.config.session_store :cookie_store, expire_after: 14.days
    

    With many things security related, there's concern that this could create vulnerabilities, but I have not been able to locate any examples of how an attacker could exploit this.

    2. Using javascript to refresh a page

    This approach involves setting a separate token that can be read by the browser, and if that token is not present, refreshing the page. Thus, when the browser loads the cached HTML (without the session cookie), executes the JS on the page, the user can be redirected or refresh the HTML.

    For example, setting a cookie for each non-protected request:

    # app/controllers/application_controller.rb
    class ApplicationController < ActionController::Base
      after_action :set_csrf_token
    
      def set_csrf_token
        cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
      end
    end
    

    Checking for this cookie in JS:

    const hasCrossSiteReferenceToken = () => document.cookie.indexOf('XSRF-TOKEN') > -1;
    
    if (!hasCrossSiteReferenceToken()) {
        location.reload();
    }
    

    This will force the browser to refresh.

    Conclusion

    I hope this helps some folks out there; this bug cost me days of work. If you're still having issues, consider reading up on:

    • Cloudflare Blog: The Curious Case of Caching CSRF Tokens
    • Cloudflare Flexible SSL mode breaks Rails 5 CSRF
    • The prepend: true bug in Devise, well described here.
    0 讨论(0)
  • 2020-11-29 20:22

    If you're using just an API you should try:

    class ApplicationController < ActionController::Base
      protect_from_forgery unless: -> { request.format.json? }
    end
    

    http://edgeapi.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html#method-i-protect_against_forgery-3F

    0 讨论(0)
  • 2020-11-29 20:22

    You have to put protect_from_forgery right before the action for authenticating user. This is the right solution

    class ApplicationController < ActionController::Base
      protect_from_forgery with: :exception
      before_action :authenticate_user!
    end
    
    0 讨论(0)
  • 2020-11-29 20:34

    Just spent the entire morning debugging this, so I thought I should share this here in case someone faces a similar issue when updating rails to 5.2 or 6.

    I had 2 problems

    1) Can't verify CSRF token authenticity.

    and, after added skipping verification,

    2) request would go through but user still wasn't logged in.

    I wasn’t caching in development

      if Rails.root.join('tmp', 'caching-dev.txt').exist?
        config.action_controller.perform_caching = true
        config.action_controller.enable_fragment_cache_logging = true
    
        config.cache_store = :memory_store
        config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{2.days.to_i}" }
      else
        config.action_controller.perform_caching = false
    
        config.cache_store = :null_store
      end
    

    And in session_store

    config.session_store :cache_store,  servers: ... 
    
    
    

    I guess app was trying to store session in cache, but it was null - so it wasn’t logging in. 

after I ran

    bin/rails dev:cache
    

    which started caching - login started to work.

    You may need to

    • Rotate master.key
    • Rotate credentials.yml.enc
    • remove secrets.yml
    0 讨论(0)
提交回复
热议问题