ActionController::InvalidAuthenticityToken in RegistrationsController#create

use*_*005 61 ruby-on-rails devise ruby-on-rails-4

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

this was error I am getting.

ActionController::InvalidAuthenticityToken

Rails.root: /home/example/app
Application Trace | Framework Trace | Full Trace

Request

Parameters:

{"utf8"=>"?",
 "user"=>{"email"=>"example@gmail.com",
 "password"=>"[FILTERED]",
 "password_confirmation"=>"[FILTERED]"},
 "x"=>"0",
 "y"=>"0"}
Run Code Online (Sandbox Code Playgroud)

this is my registrations controller

class RegistrationsController < Devise::RegistrationsController
  prepend_before_filter :require_no_authentication, :only => [ :new, :create, :cancel ]
  prepend_before_filter :authenticate_scope!, :only => [:edit, :update, :destroy]

  before_filter :configure_permitted_parameters

  prepend_view_path 'app/views/devise'

  # GET /resource/sign_up
  def new
    build_resource({})
    respond_with self.resource
  end

  # POST /resource
  def create
    build_resource(sign_up_params)

    if resource.save
      if resource.active_for_authentication?
        set_flash_message :notice, :signed_up if is_navigational_format?
        sign_up(resource_name, resource)
        respond_with resource, :location => after_sign_up_path_for(resource)
      else
        set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format?
        expire_session_data_after_sign_in!
        respond_with resource, :location => after_inactive_sign_up_path_for(resource)
      end
    else
      clean_up_passwords resource

      respond_to do |format|
        format.json { render :json => resource.errors, :status => :unprocessable_entity }
        format.html { respond_with resource }
      end
    end
  end

  # GET /resource/edit
  def edit
    render :edit
  end

  # PUT /resource
  # We need to use a copy of the resource because we don't want to change
  # the current user in place.
  def update
    self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
    prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)

    if update_resource(resource, account_update_params)
      if is_navigational_format?
        flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ?
          :update_needs_confirmation : :updated
        set_flash_message :notice, flash_key
      end
      sign_in resource_name, resource, :bypass => true
      respond_with resource, :location => after_update_path_for(resource)
    else
      clean_up_passwords resource
      respond_with resource
    end
  end

  # DELETE /resource
  def destroy
    resource.destroy
    Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
    set_flash_message :notice, :destroyed if is_navigational_format?
    respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
  end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  def cancel
    expire_session_data_after_sign_in!
    redirect_to new_registration_path(resource_name)
  end

  protected

  # Custom Fields
  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) do |u|
      u.permit(:first_name, :last_name,
        :email, :password, :password_confirmation)
    end
  end

  def update_needs_confirmation?(resource, previous)
    resource.respond_to?(:pending_reconfirmation?) &&
      resource.pending_reconfirmation? &&
      previous != resource.unconfirmed_email
  end

  # By default we want to require a password checks on update.
  # You can overwrite this method in your own RegistrationsController.
  def update_resource(resource, params)
    resource.update_with_password(params)
  end

  # Build a devise resource passing in the session. Useful to move
  # temporary session data to the newly created user.
  def build_resource(hash=nil)
    self.resource = resource_class.new_with_session(hash || {}, session)
  end

  # Signs in a user on sign up. You can overwrite this method in your own
  # RegistrationsController.
  def sign_up(resource_name, resource)
    sign_in(resource_name, resource)
  end

  # The path used after sign up. You need to overwrite this method
  # in your own RegistrationsController.
  def after_sign_up_path_for(resource)
    after_sign_in_path_for(resource)
  end

  # The path used after sign up for inactive accounts. You need to overwrite
  # this method in your own RegistrationsController.
  def after_inactive_sign_up_path_for(resource)
    respond_to?(:root_path) ? root_path : "/"
  end

  # The default url to be used after updating a resource. You need to overwrite
  # this method in your own RegistrationsController.
  def after_update_path_for(resource)
    signed_in_root_path(resource)
  end

  # Authenticates the current scope and gets the current resource from the session.
  def authenticate_scope!
    send(:"authenticate_#{resource_name}!", :force => true)
    self.resource = send(:"current_#{resource_name}")
  end

  def sign_up_params
    devise_parameter_sanitizer.sanitize(:sign_up)
  end

  def account_update_params
    devise_parameter_sanitizer.sanitize(:account_update)
  end
end
Run Code Online (Sandbox Code Playgroud)

and this is my sessions controller

class SessionsController < DeviseController
  prepend_before_filter :require_no_authentication, :only => [ :new, :create ]
  prepend_before_filter :allow_params_authentication!, :only => :create
  prepend_before_filter { request.env["devise.skip_timeout"] = true }

  prepend_view_path 'app/views/devise'

  # GET /resource/sign_in
  def new
    self.resource = resource_class.new(sign_in_params)
    clean_up_passwords(resource)
    respond_with(resource, serialize_options(resource))
  end

  # POST /resource/sign_in
  def create
    self.resource = warden.authenticate!(auth_options)
    set_flash_message(:notice, :signed_in) if is_navigational_format?
    sign_in(resource_name, resource)

    respond_to do |format|
        format.json { render :json => {}, :status => :ok }
        format.html { respond_with resource, :location => after_sign_in_path_for(resource) } 
    end
  end

  # DELETE /resource/sign_out
  def destroy
    redirect_path = after_sign_out_path_for(resource_name)
    signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
    set_flash_message :notice, :signed_out if signed_out && is_navigational_format?

    # We actually need to hardcode this as Rails default responder doesn't
    # support returning empty response on GET request
    respond_to do |format|
      format.all { head :no_content }
      format.any(*navigational_formats) { redirect_to redirect_path }
    end
  end


  protected

  def sign_in_params
    devise_parameter_sanitizer.sanitize(:sign_in)
  end

  def serialize_options(resource)
    methods = resource_class.authentication_keys.dup
    methods = methods.keys if methods.is_a?(Hash)
    methods << :password if resource.respond_to?(:password)
    { :methods => methods, :only => [:password] }
  end

  def auth_options
    { :scope => resource_name, :recall => "#{controller_path}#new" }
  end
end
Run Code Online (Sandbox Code Playgroud)

this is registration form

<%= form_for(:user, :html => {:id => 'register_form'}, :url => user_registration_path, :remote => :true, :format => :json) do |f| %>

    <div class="name_input_container">
        <div class="name_input_cell">


    <%= f.email_field :email, :placeholder => "email" %>


    <%= f.password_field :password, :placeholder => "password", :title => "8+ characters" %>


    <%= f.password_field :password_confirmation, :placeholder => "confirm password" %>


    <div class="option_buttons">
        <div class="already_registered">
            <%= link_to 'already registered?', '#', :class => 'already_registered', :id => 'already_registered', :view => 'login' %>
        </div>
        <%= image_submit_tag('modals/account/register_submit.png', :class => 'go') %>
        <div class="clear"></div>
    </div>
<% end %>
Run Code Online (Sandbox Code Playgroud)

zea*_*soi 107

Per the comments in the core application_controller.rb, set protect_from_forgery to the following:

protect_from_forgery with: :null_session
Run Code Online (Sandbox Code Playgroud)

Alternatively, per the docs, simply declaring protect_from_forgery without a :with argument will utilize :null_session by default:

protect_from_forgery # Same as above
Run Code Online (Sandbox Code Playgroud)

UPDATE:

This seems to be a documented bug in the behavior of Devise. The author of Devise suggests disabling protect_from_forgery on the particular controller action that's raising this exception:

# app/controllers/users/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
  skip_before_filter :verify_authenticity_token, :only => :create
end
Run Code Online (Sandbox Code Playgroud)

  • @AshStuart,开发与生产中的行为没有功能差异。这里存在安全问题,因为“protect_from_forgery”是防御 CSRF 攻击的主要手段。尝试此修复的任何人都可能希望研究替代安全措施,包括 IP 限制。 (2认同)

小智 32

您忘了添加<%= csrf_meta_tags %>布局文件.

例如:

<!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>
Run Code Online (Sandbox Code Playgroud)

  • 如果你看到这个问题并且在你的布局中已经有了'csrf_meta_tags`,那可能是由于调用`protect_from_forgery`钩子的顺序 - 试着确保它是你的`ApplicationController`中第一个声明的钩子(作为一个免责声明,这适用于我使用Rails 5.1.3) (3认同)

ste*_*och 20

TLDR:您可能会看到此问题,因为您的表单是通过XHR提交的.

首先要做的事情很少:

  1. Rails在页面的head标签内包含一个CSRF令牌.
  2. 只要执行POST,PATCH或DELETE请求,Rails就会评估此CSRF令牌.
  3. 当您登录或退出时,此令牌将过期

沼泽标准HTTP登录将导致整页刷新,旧的CSRF令牌将被刷新替换为Rails在您登录时创建的全新令牌.

AJAX登录不会刷新页面,因此现在无效的硬件陈旧的CSRF令牌仍然存在于您的页面上.

解决方案是在AJAX登录后手动更新HEAD标记内的CSRF令牌.


在这个问题上从一个有用的线索中无耻地借用了一些步骤.

步骤1:将新的CSRF令牌添加到成功登录后发送的响应标头中

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
Run Code Online (Sandbox Code Playgroud)

第2步:使用jQuery在ajaxComplete事件触发时使用新值更新页面:

$(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);
  }
});
Run Code Online (Sandbox Code Playgroud)

而已.YMMV取决于您的Devise配置.我怀疑这个问题最终是由于旧的CSRF令牌正在杀死请求,并且rails引发异常.


Fra*_*Jr. 12

如果您只使用API​​,则应尝试:

class ApplicationController < ActionController::Base
  protect_from_forgery unless: -> { request.format.json? }
end
Run Code Online (Sandbox Code Playgroud)

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


Chr*_*rds 11

对于Rails的5这可能是由于在该命令protect_from_forgery和你before_actions被触发.

最近,我遇到了类似的情况,即使protect_from_forgery with: :exception是在第一线ApplicationControllerbefore_action的仍然干扰.

解决方案是改变:

protect_from_forgery with: :exception
Run Code Online (Sandbox Code Playgroud)

至:

protect_from_forgery prepend: true, with: :exception
Run Code Online (Sandbox Code Playgroud)

这里有一篇关于它的博客文章http://blog.bigbinary.com/2016/04/06/rails-5-default-protect-from-forgery-prepend-false.html


Mat*_*att 6

浏览器缓存 HTML 问题 (2020)

如果您已尝试此页面上的所有补救措施,但仍然遇到InvalidAuthenticityToken异常问题,则可能与浏览器缓存 HTML 有关。Github 上有一个问题,有数百条评论以及一些可重现的代码。简而言之,这就是我遇到的与 HTML 缓存相关的情况:

  1. 用户浏览网站。Rails 在第一个 GET 请求上设置一个签名的会话 cookie。请config/initializers/session_store.rb参阅配置选项。此会话 cookie 存储有用的信息,包括用于解密和验证请求真实性的 CSRF 令牌。重要提示:默认情况下,会话 cookie 将在浏览器窗口关闭时过期。
  2. 用户浏览到包含表单的页面。对我来说,我在登录页面上收到的异常最多。
  3. Rails 在此表单中嵌入隐藏的 CSRF 令牌,并将此令牌与表单数据一起提交。重要提示:此令牌嵌入在 HTML 中。
  4. ActionController 从 params 对象中获取 CSRF 令牌,并使用verified_request?Rails 4.2+ 中的方法使用 cookie 中的 CSRF 令牌对其进行验证。

许多浏览器现在都实现了 HTML 缓存,因此当您打开页面时,无需请求即可加载 HTML。不幸的是,当浏览器关闭时,会话 cookie 会被销毁,因此如果用户在表单(例如登录页面)上关闭浏览器,则第一个请求将不会包含 CSRF 令牌,从而引发 InvalidAuthenticityError。

两种常见的解决方案

  1. 将会话 cookie 的有效期延长到浏览器窗口之外。
  2. 在浏览器中检测会话 cookie 是否丢失(通过代理 cookie),如果丢失则刷新页面。

1. 延长会话cookie有效期

正如Github 评论中所述,Django 采用了这种方法:

Django put 将令牌添加到它自己的名为 CSRF_COOKIE 的 cookie 中。这是一个持久性 cookie,一年后过期。如果发出后续请求,则 cookie 的有效期会更新。

在轨道中:

# config/initializers/session_store.rb 
Rails.application.config.session_store :cookie_store, expire_after: 14.days
Run Code Online (Sandbox Code Playgroud)

对于许多与安全相关的事情,人们担心这可能会产生漏洞,但我无法找到攻击者如何利用此漏洞的任何示例。

2.使用javascript刷新页面

此方法涉及设置一个可由浏览器读取的单独令牌,如果该令牌不存在,则刷新页面。这样,当浏览器加载缓存的 HTML(不带会话 cookie)、执行页面上的 JS 时,可以重定向用户或刷新 HTML。

例如,为每个不受保护的请求设置 cookie:

# 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
Run Code Online (Sandbox Code Playgroud)

在 JS 中检查此 cookie:

const hasCrossSiteReferenceToken = () => document.cookie.indexOf('XSRF-TOKEN') > -1;

if (!hasCrossSiteReferenceToken()) {
    location.reload();
}
Run Code Online (Sandbox Code Playgroud)

这将强制浏览器刷新。

结论

我希望这可以帮助一些人;这个错误花费了我几天的时间。如果您仍然遇到问题,请考虑阅读:


And*_*nga 5

刚刚花了整个早上调试这个,所以我想我应该在这里分享这个,以防有人在将 Rails 更新到 5.2 或 6 时遇到类似的问题。

\n\n

我有 2 个问题

\n\n

1) 无法验证CSRF令牌的真实性。

\n\n

并且,在添加跳过验证后,

\n\n

2) 请求将通过,但用户仍未登录。

\n\n

我在开发中\xe2\x80\x99t 缓存

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

在session_store中

\n\n
config.session_store :cache_store,  servers: ... \xe2\x80\xa8    \xe2\x80\xa8    \n
Run Code Online (Sandbox Code Playgroud)\n\n

我猜应用程序试图将会话存储在缓存中,但它是空的 - 所以它不是 \xe2\x80\x99t 登录。 \xe2\x80\xa8\xe2\x80\xa8 在我运行之后

\n\n
bin/rails dev:cache\n
Run Code Online (Sandbox Code Playgroud)\n\n

开始缓存 - 登录开始工作。

\n\n

您可能需要

\n\n
    \n
  • 轮换主密钥
  • \n
  • 轮换凭证.yml.enc
  • \n
  • 删除secrets.yml
  • \n
\n