Rails CSRF Protection + Angular.js: protect_from_forgery me hace cerrar sesión en POST

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 

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.

La gem 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 gem de autenticación Devise .

Antes que nada, agrega la gem:

 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') 

Vi las otras respuestas y pensé que eran geniales y estaban bien pensadas. Conseguí que funcionara mi aplicación de Rails, aunque con lo que pensé que era una solución más simple, así que pensé en compartirla. Mi aplicación de Rails 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.

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 stack 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 

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:

 

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.

  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!