ActionController:: InvalidAuthenticityToken в RegistrationsController#create



Привет я использую Devise для моей аутентификации пользователя вдруг моя новая регистрация пользователя не работает.



это была ошибка, которую я получаю.



ActionController::InvalidAuthenticityToken

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

Request

Parameters:

{"utf8"=>"✓",
"user"=>{"email"=>"[email protected]",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"},
"x"=>"0",
"y"=>"0"}


Это мой контроллер регистрации



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


а это мой контроллер сеансов



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


это регистрационная форма



<%= 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 %>
647   6  

6 ответов:

на комментарии в основном application_controller.rb, set protect_from_forgery следующим образом:

protect_from_forgery with: :null_session

как вариант на документы, просто объявив protect_from_forgeryбез :with аргумент использовать :null_session по умолчанию:

protect_from_forgery # Same as above

обновление:

кажется документированная ошибка в поведении Devise. Автор изобретения предполагает отключение protect_from_forgery на конкретном действии контроллера, которое вызывает это исключение:

# app/controllers/users/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
  skip_before_filter :verify_authenticity_token, :only => :create
end

Вы забыли добавить <%= 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>

TLDR: вы, вероятно, видите эту проблему, потому что ваша форма отправляется через XHR.

первые несколько вещей:

  1. Rails включает маркер CSRF внутри тега head вашей страницы.
  2. Rails оценивает этот токен CSRF в любое время, когда вы выполняете запрос POST, PATCH или DELETE.
  3. этот маркер истекает когда вы входите или выходите

стандартный HTTP-вход в bog вызовет полную страницу обновите, и старый токен CSRF будет вспыхнул и заменить С совершенно новым, который Rails создает при входе в систему.

вход AJAX будет не обновите страницу, так что хрустящий старый, устаревший маркер CSRF, который теперь недействителен, все еще присутствует на Вашей странице.

решение состоит в том, чтобы обновить токен CSRF внутри тега HEAD вручную после входа AJAX.


некоторые шаги, которые я бессовестно заимствовано из полезного нить по этому вопросу.

Шаг 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

Шаг 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);
  }
});

вот именно. YMMV в зависимости от вашей конфигурации Devise. Я подозреваю, что эта проблема в конечном итоге вызвана тем, что старый токен CSRF убивает запрос, и rails выдает исключение.

Если вы используете только API, вы должны попробовать:

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

для рельсов 5 это может быть связано с порядком, в котором protect_from_forgery и свой before_actions срабатывает.

недавно я столкнулся с подобной ситуацией, хотя protect_from_forgery with: :exception была первая строка ApplicationController на before_actionони все еще вмешивались.

решение было изменить:

protect_from_forgery with: :exception

to:

protect_from_forgery prepend: true, with: :exception

там в блоге об этом здесь http://blog.bigbinary.com/2016/04/06/rails-5-default-protect-from-forgery-prepend-false.html

вы должны поставить protect_from_forgery прямо перед действием для аутентификации пользователя. Это правильное решение

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :authenticate_user!
end

Comments

    Ничего не найдено.