Rails: impossible de vérifier l'authenticité du jeton CSRF lors d'une demande POST

89

Je veux faire POST requestà mon développeur local, comme ceci:

  HTTParty.post('http://localhost:3000/fetch_heroku',
                :body => {:type => 'product'},)

Cependant, à partir de la console du serveur, il signale

Started POST "/fetch_heroku" for 127.0.0.1 at 2016-02-03 23:33:39 +0800
  ActiveRecord::SchemaMigration Load (0.0ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by AdminController#fetch_heroku as */*
  Parameters: {"type"=>"product"}
Can't verify CSRF token authenticity
Completed 422 Unprocessable Entity in 1ms

Voici ma configuration de contrôleur et de routes, c'est assez simple.

  def fetch_heroku
    if params[:type] == 'product'
      flash[:alert] = 'Fetch Product From Heroku'
      Heroku.get_product
    end
  end

  post 'fetch_heroku' => 'admin#fetch_heroku'

Je ne suis pas sûr de ce que je dois faire? Désactiver le CSRF fonctionnerait certainement, mais je pense que ce devrait être mon erreur lors de la création d'une telle API.

Dois-je faire une autre configuration?

cqcn1991
la source
5
Pour les API, il est généralement accepté de désactiver la validation des jetons CSRF . J'utilise protect_from_forgery with: :null_session.
dcestari

Réponses:

110

La falsification de demande intersite (CSRF / XSRF) se produit lorsqu'une page Web malveillante incite les utilisateurs à exécuter une demande qui n'est pas destinée, par exemple en utilisant des bookmarklets, des iframes ou simplement en créant une page suffisamment similaire visuellement pour tromper les utilisateurs.

La protection Rails CSRF est conçue pour les applications Web "classiques" - elle donne simplement une certaine assurance que la demande provient de votre propre application Web. Un jeton CSRF fonctionne comme un secret que seul votre serveur connaît - Rails génère un jeton aléatoire et le stocke dans la session. Vos formulaires envoient le jeton via une entrée masquée et Rails vérifie que toute demande non GET inclut un jeton qui correspond à ce qui est stocké dans la session.

Cependant, une API est généralement par définition intersite et destinée à être utilisée dans plus que votre application Web, ce qui signifie que tout le concept de CSRF ne s'applique pas tout à fait.

Au lieu de cela, vous devez utiliser une stratégie basée sur des jetons pour authentifier les demandes d'API avec une clé API et un secret, car vous vérifiez que la demande provient d'un client API approuvé et non de votre propre application.

Vous pouvez désactiver CSRF comme indiqué par @dcestari:

class ApiController < ActionController::Base
  protect_from_forgery with: :null_session
end

Mis à jour. Dans Rails 5, vous pouvez générer uniquement des applications API en utilisant l' --apioption:

rails new appname --api

Ils n'incluent pas le middleware CSRF et de nombreux autres composants qui sont superflouus.

max
la source
Merci, j'ai choisi de désactiver une partie du CSRF: stackoverflow.com/questions/5669322/…
cqcn1991
4
cela suggère que toutes les API desservent le trafic d'application à application (dans lequel un seul partenaire reçoit une clé et un secret uniques). Dans ce scénario, comme une communication de serveur à serveur, cette réponse est appropriée. CEPENDANT, ce qui est déroutant pour la plupart des développeurs d'applications Web, c'est que pour un client Javascript, contrôlé et écrit par vous, vous NE VOULEZ PAS utiliser un seul secret de clé (qui exposerait un seul secret de clé à tous les clients). Au lieu de cela, le mécanisme de session CSRF et cookie de Rail fonctionne très bien, même pour les applications Javascript qui utilisent votre API, si vous renvoyez le jeton CSRF à Rails à chaque requête.
Jason FB
la façon dont vous faites cela dans AJAX est décrite dans ce post de SO stackoverflow.com/questions/7203304/...
Jason FB
@JasonFB vous avez raison de dire qu'un seul secret avec des clients javascript ne fonctionne pas. Cependant, l'utilisation de sessions et de la protection Rails CSRF pose toujours problème si vous envisagez de créer une API qui peut également être utilisée par d'autres types de clients non-navigateur.
max
Si vous fournissez ou créez une alternative à CSRF, soyez mon invité. Sachez simplement la raison de ce que vous remplacez afin de savoir par quoi il convient de le remplacer. Évidemment, maintenant notre chipotage devient contextuel.
Jason FB
77

Une autre façon de désactiver CSRF qui ne rendra pas une session nulle consiste à ajouter:

skip_before_action :verify_authenticity_token

dans votre contrôleur Rails. Cela garantira que vous avez toujours accès aux informations de session.

Encore une fois, assurez-vous de ne le faire que dans les contrôleurs d'API ou dans d'autres endroits où la protection CSRF ne s'applique pas tout à fait.

Matt Waldron
la source
1
C'est la même chose que protect_from_forgery except: [:my_method_name]?
Arnold Roa
19

Il y a des informations pertinentes sur une configuration de CSRF par rapport aux contrôleurs d'API sur api.rubyonrails.org :

Il est important de se rappeler que les requêtes XML ou JSON sont également affectées et si vous créez une API, vous devez modifier la méthode de protection contre la falsification dans ApplicationController(par défaut:) :exception:

class ApplicationController < ActionController::Base
  protect_from_forgery unless: -> { request.format.json? }
end

Nous souhaitons peut-être désactiver la protection CSRF pour les API, car elles sont généralement conçues pour être sans état. Autrement dit, le client API de requête gérera la session pour vous au lieu de Rails.

M. Tao
la source
4
C'est tellement déroutant. Je oscille entre les sources qui disent que protect_from_forgeryc'est toujours nécessaire pour les API et les sources qui disent que ce n'est pas le cas. Que faire si l'API est destinée à une application à page unique, qui utilise le cookie de session pour l'authentification de l'utilisateur?
Wylliam Judd
Le mécanisme CSRF est le moyen intégré de Rails pour gérer le vecteur d'attaque à l'aide de cookies de session. Le mécanisme protège vos contrôleurs, tout en fonctionnant à côté (en fait à l'intérieur) du cookie de session Rails. Sans protect_from_forgery, ou en le désactivant ou en définissant une exception dessus, vous dites à Rails de NE PAS protéger cette action en utilisant les informations du jeton CSRF (qui sont extraites du cookie de session).
Jason FB
5
Je pense que ce qui est déroutant, c'est que pour la documentation Rails, «API» signifie une application de serveur à serveur qui recevra les demandes d'API de partenaires distants de confiance (pas tous les utilisateurs Web qui visitent votre site). Pour ces personnes, donnez des paires clé-secret uniques via un mécanisme sécurisé non piratable. Pour les applications Web modernes, où de nombreuses personnes chargeront votre application Web via un client Web, vous utilisez toujours une session ou un mécanisme basé sur des jetons pour identifier de manière unique chaque personne qui visite votre site. Donc, à moins que vous n'utilisiez UN AUTRE mécanisme pour faire cela (jetons Web Json, etc.), restez fidèle aux éléments intégrés de Rails.
Jason FB
1
la façon dont vous faites cela dans AJAX est décrite dans ce post de SO stackoverflow.com/questions/7203304/...
Jason FB
12

Depuis Rails 5, vous pouvez également créer une nouvelle classe avec :: API au lieu de :: Base:

class ApiController < ActionController::API
end
webaholik
la source
3

Si vous souhaitez exclure l'action d'échantillon du contrôleur d'échantillons

class TestController < ApplicationController
  protect_from_forgery :except => [:sample]

  def sample
     render json: @hogehoge
  end
end

Vous pouvez traiter les demandes de l'extérieur sans aucun problème.

Ryosuke Hujisawa
la source
0

La solution la plus simple au problème est de faire des choses standard dans votre contrôleur ou vous pouvez directement le mettre dans ApplicationController

class ApplicationController < ActionController::Base protect_from_forgery with: :exception, prepend: true end

Arish Khan
la source