ActionController:: InvalidAuthenticityToken en RegistrationsController#create


Hola estoy usando Devise para mi autenticación de usuario de repente mi nuevo registro de usuario no estaba funcionando.

Este fue un error que estoy recibiendo.

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"}

Este es mi controlador de registros

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

Y este es mi controlador de sesiones

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

Este es el formulario de inscripción

<%= 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 %>
Author: user3144005, 2014-01-02

6 answers

Por los comentarios en el núcleo application_controller.rb, establece protect_from_forgery a lo siguiente:

protect_from_forgery with: :null_session

Alternatively , per the docs , simply declaring protect_from_forgery sin un argumento :with utilizará :null_session por defecto:

protect_from_forgery # Same as above

ACTUALIZACIÓN :

Esto parece ser un error documentado en el comportamiento de Devise. El autor de Devise sugiere desactivar protect_from_forgery en la acción del controlador en particular que está elevando esto excepción:

# app/controllers/users/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
  skip_before_filter :verify_authenticity_token, :only => :create
end
 101
Author: zeantsoi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-02 04:08:14

Ha olvidado agregar <%= csrf_meta_tags %> en el lado de su archivo de diseño.

Ej:

<!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>
 31
Author: Snm Maurya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-08 08:25:18

TLDR: Probablemente esté viendo este problema porque su formulario se envía a través de XHR.

Pocas cosas primero:

  1. Rails incluye un token CSRF dentro de la etiqueta head de su página.
  2. Rails evalúa este token CSRF cada vez que realiza una solicitud POST, PATCH o DELETE.
  3. Este token caduca cuando inicia sesión o cierra sesión

Un inicio de sesión HTTP estándar de bog causará una actualización de página completa, y el token CSRF antiguo será limpiado y reemplazado con el nuevo que Rails crea al iniciar sesión.

Un inicio de sesión AJAX no actualizará la página, por lo que el viejo y rancio token CSRF, que ahora no es válido, todavía está presente en su página.

La solución es actualizar el token CSRF dentro de su etiqueta HEAD manualmente después de iniciar sesión en AJAX.


Algunos pasos que he tomado prestados descaradamente de un útil hilo en este asunto.

Paso 1: Agregue el nuevo CSRF-token a los encabezados de respuesta que se envían después de un inicio de sesión exitoso

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

Paso2: Use jQuery para actualizar la página con los nuevos valores cuando se inicie el evento 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);
  }
});

Eso es todo. YMMV dependiendo de la configuración del dispositivo. Sospecho, sin embargo, que este problema es causado en última instancia por el hecho de que el viejo token CSRF está matando la solicitud, y rails lanza una excepción.

 19
Author: stephenmurdoch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:26

Si estás usando solo una API deberías probar:

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

 9
Author: Franzé Jr.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-12 22:19:17

Para Rails 5 podría deberse al orden en el que protect_from_forgery y tu before_actions se activan.

Me enfrenté a una situación similar recientemente, a pesar de que protect_from_forgery with: :exception era la primera línea en el ApplicationController, el before_action's todavía estaban interfiriendo.

La solución fue cambiar:

protect_from_forgery with: :exception

A:

protect_from_forgery prepend: true, with: :exception

Hay una entrada de blog sobre esto aquí http://blog.bigbinary.com/2016/04/06/rails-5-default-protect-from-forgery-prepend-false.html

 7
Author: Chris Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-28 09:00:16

Tienes que poner protect_from_forgery justo antes de la acción para autenticar al usuario. Esta es la solución correcta

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :authenticate_user!
end
 1
Author: Richard Lapiš,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-31 10:38:07