ruby on rails - gema - ActionController:: InvalidAuthenticityToken en RegistrationsController#create
devise rails español (6)
Debes 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
Hola, estoy usando Devise para mi autenticación de usuario. De repente mi nuevo registro de usuario no funcionaba.
esto 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
esto es un formulario de registro
<%= 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 %>
Ha olvidado agregar <%= csrf_meta_tags %>
al costado de su archivo de diseño.
p.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>
Para Rails 5 podría deberse al orden en que protect_from_forgery
y tus before_actions
se activan.
Me enfrenté a una situación similar recientemente, aunque protect_from_forgery with: :exception
fue la primera línea en el ApplicationController
, las before_action
todavía estaban interfiriendo.
La solución fue cambiar:
protect_from_forgery with: :exception
a:
protect_from_forgery prepend: true, with: :exception
Hay una publicación en el blog aquí http://blog.bigbinary.com/2016/04/06/rails-5-default-protect-from-forgery-prepend-false.html
Según los comentarios en el núcleo application_controller.rb
, establezca protect_from_forgery
en lo siguiente:
protect_from_forgery with: :null_session
Alternativamente , según los documentos , simplemente declarar protect_from_forgery
sin un argumento :with
utilizará :null_session
por defecto:
protect_from_forgery # Same as above
ACTUALIZAR :
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 particular que está planteando esta excepción:
# app/controllers/users/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token, :only => :create
end
Si está utilizando solo una API, debería intentar:
class ApplicationController < ActionController::Base
protect_from_forgery unless: -> { request.format.json? }
end
TLDR: probablemente esté viendo este problema porque su formulario se envía a través de XHR.
Pocas cosas primero:
- Rails incluye un token CSRF dentro de la etiqueta principal de su página.
- Rails evalúa este token CSRF cada vez que realiza una solicitud POST, PATCH o DELETE.
- Este token caduca al iniciar o cerrar sesión
Un inicio de sesión HTTP estándar de pantano causará una actualización de página completa, y el token de CSRF antiguo se eliminará y se reemplazará por uno nuevo que Rails crea al iniciar sesión.
Un inicio de sesión de AJAX no actualizará la página, por lo que el antiguo token de CSRF obsoleto, que ahora no es válido, aún está presente en su página.
La solución es actualizar manualmente el token CSRF dentro de su etiqueta HEAD después de iniciar sesión en AJAX.
Algunos pasos que he tomado descaradamente de un hilo útil sobre 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 correcto.
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
Paso 2: use jQuery para actualizar la página con los nuevos valores cuando se ajaxComplete
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. YMMV dependiendo de su configuración Devise. Sin embargo, sospecho que este problema en última instancia es causado por el hecho de que el token viejo de CSRF está matando la solicitud y Rails lanza una excepción.