ruby on rails - Rails CSRF Protection+Angular.js: protect_from_forgery me hace cerrar sesión en POST
ruby-on-rails angularjs (8)
Si se menciona la opción protect_from_forgery
en application_controller, entonces puedo iniciar sesión y realizar cualquier solicitud GET, pero en la primera solicitud POST, Rails restablece la sesión, lo que me desconecta.
Desactivé temporalmente la opción protect_from_forgery
, pero me gustaría usarla con Angular.js. ¿Hay alguna manera de hacer eso?
Creo que leer CSRF-value de DOM no es una buena solución, solo es una solución.
Aquí hay un documento en el sitio web oficial de angularJS http://docs.angularjs.org/api/ng.$http :
Como solo el JavaScript que se ejecuta en su dominio puede leer la cookie, su servidor puede estar seguro de que el XHR proviene de JavaScript ejecutándose en su dominio.
Para aprovechar esto (Protección CSRF), su servidor necesita establecer un token en una cookie de sesión legible por JavaScript llamada XSRF-TOKEN en la primera solicitud HTTP GET. En solicitudes posteriores no GET, el servidor puede verificar que la cookie coincida con el encabezado X-XSRF-TOKEN HTTP
Aquí está mi solución basada en esas instrucciones:
Primero, configure la cookie:
# app/controllers/application_controller.rb
# Turn on request forgery protection
protect_from_forgery
after_action :set_csrf_cookie
def set_csrf_cookie
cookies[''XSRF-TOKEN''] = form_authenticity_token if protect_against_forgery?
end
Entonces, debemos verificar el token en cada solicitud no GET.
Dado que Rails ya ha construido con el método similar, podemos simplemente anularlo para agregar nuestra lógica:
# app/controllers/application_controller.rb
protected
# In Rails 4.2 and above
def verified_request?
super || valid_authenticity_token?(session, request.headers[''X-XSRF-TOKEN''])
end
# In Rails 4.1 and below
def verified_request?
super || form_authenticity_token == request.headers[''X-XSRF-TOKEN'']
end
Encontré un truco muy rápido para esto. Todo lo que tuve que hacer es lo siguiente:
a. En mi opinión, inicializo una variable $scope
que contiene el token, digamos antes del formulario, o incluso mejor en la inicialización del controlador:
<div ng-controller="MyCtrl" ng-init="authenticity_token = ''<%= form_authenticity_token %>''">
segundo. En mi controlador AngularJS, antes de guardar mi nueva entrada, agrego el token al hash:
$scope.addEntry = ->
$scope.newEntry.authenticity_token = $scope.authenticity_token
entry = Entry.save($scope.newEntry)
$scope.entries.push(entry)
$scope.newEntry = {}
No hay nada más que hacer.
La gema angular_rails_csrf agrega soporte automáticamente para el patrón descrito en la respuesta de HungYuHei a todos sus controladores:
# Gemfile
gem ''angular_rails_csrf''
La respuesta que combina todas las respuestas anteriores y confía en que está utilizando la gema de autenticación Devise
.
Antes que nada, agrega la gema:
gem ''angular_rails_csrf''
A continuación, agregue rescue_from
block a application_controller.rb:
protect_from_forgery with: :exception
rescue_from ActionController::InvalidAuthenticityToken do |exception|
cookies[''XSRF-TOKEN''] = form_authenticity_token if protect_against_forgery?
render text: ''Invalid authenticity token'', status: :unprocessable_entity
end
Y finalmente, agrega el módulo interceptor a tu aplicación angular.
# coffee script
app.factory ''csrfInterceptor'', [''$q'', ''$injector'', ($q, $injector) ->
responseError: (rejection) ->
if rejection.status == 422 && rejection.data == ''Invalid authenticity token''
deferred = $q.defer()
successCallback = (resp) ->
deferred.resolve(resp)
errorCallback = (resp) ->
deferred.reject(resp)
$http = $http || $injector.get(''$http'')
$http(rejection.config).then(successCallback, errorCallback)
return deferred.promise
$q.reject(rejection)
]
app.config ($httpProvider) ->
$httpProvider.interceptors.unshift(''csrfInterceptor'')
Si está utilizando la protección CSRF de Rails predeterminada ( <%= csrf_meta_tags %>
), puede configurar su módulo angular de esta manera:
myAngularApp.config ["$httpProvider", ($httpProvider) ->
$httpProvider.defaults.headers.common[''X-CSRF-Token''] = $(''meta[name=csrf-token]'').attr(''content'')
]
O, si no está usando CoffeeScript (¿qué?):
myAngularApp.config([
"$httpProvider", function($httpProvider) {
$httpProvider.defaults.headers.common[''X-CSRF-Token''] = $(''meta[name=csrf-token]'').attr(''content'');
}
]);
Si lo prefiere, puede enviar el encabezado solo en solicitudes no GET con algo como lo siguiente:
myAngularApp.config ["$httpProvider", ($httpProvider) ->
csrfToken = $(''meta[name=csrf-token]'').attr(''content'')
$httpProvider.defaults.headers.post[''X-CSRF-Token''] = csrfToken
$httpProvider.defaults.headers.put[''X-CSRF-Token''] = csrfToken
$httpProvider.defaults.headers.patch[''X-CSRF-Token''] = csrfToken
$httpProvider.defaults.headers.delete[''X-CSRF-Token''] = csrfToken
]
Además, asegúrese de verificar la respuesta de HungYuHei , que cubre todas las bases en el servidor en lugar del cliente.
Utilicé el contenido de la respuesta de HungYuHei en mi aplicación. Sin embargo, descubrí que estaba lidiando con algunos problemas adicionales, algunos debido a mi uso de Devise para la autenticación y otros debido a la falla que obtuve con mi aplicación:
protect_from_forgery with: :exception
Observé la pregunta relacionada con el desbordamiento de pila y las respuestas allí , y escribí una publicación de blog mucho más detallada que resume las diversas consideraciones. Las partes de esa solución que son relevantes aquí son, en el controlador de la aplicación:
protect_from_forgery with: :exception
after_filter :set_csrf_cookie_for_ng
def set_csrf_cookie_for_ng
cookies[''XSRF-TOKEN''] = form_authenticity_token if protect_against_forgery?
end
rescue_from ActionController::InvalidAuthenticityToken do |exception|
cookies[''XSRF-TOKEN''] = form_authenticity_token if protect_against_forgery?
render :error => ''Invalid authenticity token'', {:status => :unprocessable_entity}
end
protected
def verified_request?
super || form_authenticity_token == request.headers[''X-XSRF-TOKEN'']
end
Vi las otras respuestas y pensé que eran geniales y estaban bien pensadas. Conseguí que funcionara mi aplicación de rieles, aunque con lo que pensé que era una solución más simple, así que pensé en compartirla. Mi aplicación de rieles llegó con esto incumplido,
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
Leí los comentarios y parecía que eso es lo que quiero usar angular y evitar el error csrf. Lo cambié a esto,
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
end
¡Y ahora funciona! No veo ninguna razón por la cual esto no debería funcionar, pero me gustaría escuchar algunas ideas de otros carteles.
angular
.module(''corsInterceptor'', [''ngCookies''])
.factory(
''corsInterceptor'',
function ($cookies) {
return {
request: function(config) {
config.headers["X-XSRF-TOKEN"] = $cookies.get(''XSRF-TOKEN'');
return config;
}
};
}
);
¡Está trabajando en el lado angularjs!