web-dev-qa-db-fra.com

Rails Protection CSRF + Angular.js: protect_from_forgery me permet de me déconnecter POST

Si la protect_from_forgery option est mentionnée dans application_controller, je peux alors me connecter et exécuter toutes les requêtes GET, mais le tout premier POST request Rails réinitialise la session, qui enregistre moi dehors.

J'ai tourné le protect_from_forgery Option désactivée temporairement, mais vous souhaitez l’utiliser avec Angular.js. Y a-t-il un moyen de faire ça?

126
Paul

Je pense que lire CSRF-value à partir de DOM n'est pas une bonne solution, c'est simplement une solution de contournement.

Voici un document sous forme de document site officiel angularJS http://docs.angularjs.org/api/ng.$http :

Étant donné que seul le code JavaScript utilisé sur votre domaine peut lire le cookie, votre serveur peut être assuré que le XHR provient de JavaScript exécuté sur votre domaine.

Pour tirer parti de cette protection (protection CSRF), votre serveur doit définir un jeton dans un cookie de session lisible en JavaScript appelé XSRF-TOKEN lors de la première requête HTTP GET. Lors de demandes ultérieures non-GET, le serveur peut vérifier que le cookie correspond à l'en-tête HTTP X-XSRF-TOKEN.

Voici ma solution basée sur ces instructions:

Tout d'abord, définissez le 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

Ensuite, nous devrions vérifier le jeton sur chaque demande non-GET.
Depuis Rails a déjà été construit avec la même méthode, nous pouvons simplement le remplacer pour ajouter notre logique:

# 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
275
HungYuHei

Si vous utilisez la valeur par défaut Rails Protection CSRF (<%= csrf_meta_tags %>), vous pouvez configurer votre module Angular comme ceci:

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
]

Ou, si vous n'utilisez pas CoffeeScript (quoi !?):

myAngularApp.config([
  "$httpProvider", function($httpProvider) {
    $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
  }
]);

Si vous préférez, vous pouvez envoyer l'en-tête uniquement sur des demandes non-GET avec les éléments suivants:

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
]

Aussi, assurez-vous de vérifier réponse de HungYuHei , qui couvre toutes les bases sur le serveur plutôt que sur le client.

78
Michelle Tilley

La gemme angular_Rails_csrf ajoute automatiquement la prise en charge du modèle décrit dans réponse de HungYuHei à tous vos contrôleurs:

# Gemfile
gem 'angular_Rails_csrf'
29
jsanders

La réponse qui fusionne toutes les réponses précédentes et repose sur le fait que vous utilisez la gem d'authentification Devise.

Tout d'abord, ajoutez la gemme:

gem 'angular_Rails_csrf'

Ensuite, ajoutez rescue_from bloc dans 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

Et enfin, ajouter le module intercepteur angular app.

# 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')
4
Anton Orel

J'ai vu les autres réponses et je les ai trouvées géniales et bien pensées. J'ai eu mon Rails fonctionnant avec ce que je pensais être une solution plus simple, donc je pensais partager. Mon Rails est venue avec ce défaut dans son ,

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

J'ai lu les commentaires et il me semblait que c'était ce que je voulais utiliser angular et éviter l'erreur csrf. Je l'ai changé en ceci,

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

Et maintenant ça marche! Je ne vois aucune raison pour que cela ne fonctionne pas, mais j'aimerais entendre un aperçu des autres affiches.

1
Blaine Hatab

J'ai trouvé un hack très rapide à cela. Tout ce que j'avais à faire est le suivant:

une. À mon avis, j'initialise un $scope variable qui contient le jeton, disons avant le formulaire, voire mieux à l’initialisation du contrôleur:

<div ng-controller="MyCtrl" ng-init="authenticity_token = '<%= form_authenticity_token %>'">

b. Dans mon contrôleur AngularJS, avant d’enregistrer ma nouvelle entrée, j’ajoute le jeton au hachage:

$scope.addEntry = ->
    $scope.newEntry.authenticity_token = $scope.authenticity_token 
    entry = Entry.save($scope.newEntry)
    $scope.entries.Push(entry)
    $scope.newEntry = {}

Il ne reste plus rien à faire.

1
Ruby Racer

J'ai utilisé le contenu de la réponse de HungYuHei dans ma candidature. J'ai toutefois constaté quelques problèmes supplémentaires, certains à cause de mon utilisation de Devise pour l'authentification et d'autres à cause du défaut de ma candidature:

protect_from_forgery with: :exception

Je note le connexe question de dépassement de pile et les réponses ici , et j'ai écrit un beaucoup plus verbeux article de blog qui résume les diverses considérations. Les parties de cette solution qui sont pertinentes ici sont, dans le contrôleur d’application:

  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
1
PaulL
 angular
  .module('corsInterceptor', ['ngCookies'])
  .factory(
    'corsInterceptor',
    function ($cookies) {
      return {
        request: function(config) {
          config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
          return config;
        }
      };
    }
  );

Ça marche côté angularjs!

0
Evgeniy Krokhmal