web-dev-qa-db-fra.com

Phoenix - Erreur de jeton CSRF (Cross Site Forgery Protection) non valide

Je reçois une erreur de jeton CSRF non valide lorsque je tente de mettre à jour (ou de créer) un enregistrement. J'utilise Elixir v1.0.3, Erlang/OTP 17 [erts-6.3] et Phoenix v0.8.0 (je ne suis pas sûr de savoir comment vérifier la version de Phoenix). Je crée une application Web en suivant principalement les guides Phoenix et les ressources Elixir Dose Jobsite Example. Toutefois, lorsque j'essaie de publier des informations à partir d'un formulaire HTML, l'erreur d'erreur de jeton CSRF non valide apparaît. En suivant les conseils donnés dans l'erreur, j'ai ajouté 'x-csrf-token': csrf_token à l'action.

edit.html.eex:

<h2>Edit Directory</h2>
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id, 'x-csrf-token': @csrf_token %>" method="post">
  <div class="form-group">
    <label for="directory" class="col-sm-2 control-label">Directory</label>
    <div class="col-sm-10">
      <input type="hidden" name="_method" value="PATCH">
      <input type="text" class="form-control" value="<%= @directory.directory %>" name="directory" placeholder="Directory" required="required">
    </div>
  </div>
...

mais je reçois l'erreur suivante:

[error] #PID<0.579.0> running Ainur.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /config/directories/2?x-csrf-token=
** (exit) an exception was raised:
    ** (Plug.CSRFProtection.InvalidCSRFTokenError) Invalid CSRF (Cross Site Forgery Protection) token. Make sure that all your non-HEAD and non-GET requests include the csrf_token as part of form params or as a value in your request's headers with the key 'x-csrf-token'
        (plug) lib/plug/csrf_protection.ex:54: Plug.CSRFProtection.call/2
        (ainur) web/router.ex:4: Ainur.Router.browser/2
        (ainur) lib/phoenix/router.ex:2: Ainur.Router.call/2
        (plug) lib/plug/debugger.ex:104: Plug.Debugger.wrap/3
        (phoenix) lib/phoenix/endpoint/error_handler.ex:43: Phoenix.Endpoint.ErrorHandler.wrap/3
        (ainur) lib/ainur/endpoint.ex:1: Ainur.Endpoint.phoenix_endpoint_pipeline/2
        (plug) lib/plug/debugger.ex:104: Plug.Debugger.wrap/3
        (phoenix) lib/phoenix/endpoint/error_handler.ex:43: Phoenix.Endpoint.ErrorHandler.wrap/3

Autant que je sache (étant nouveau pour Elixir, Phoenix et HTML), "action" est essentiellement un chemin et tous les paramètres que je place y seront renvoyés à l'application. Et, en effet, je trouve que x-csrf-token = "" est renvoyé au routeur, donc @csrf_token ne doit pas être correct. Je ne sais pas exactement d'où vient le csrf_token, je ne sais donc pas comment le référencer (ou peut-être que je le fais complètement de travers).

Toutes les idées seraient grandement appréciées.

17
Paul B

Pour voir la version installée, lancez

cat ./deps/phoenix/mix.exs | grep version

Ce qui vous montre quel phénix vous avez dans le répertoire deps.

De plus, si/lorsque vous mettez à niveau vers phoenix 0.9.0, les choses ont changé (en raison des mises à jour de Plug.CSRFProtection), le CSRF fonctionne différemment en utilisant des cookies au lieu de sessions.

De Phoenix changelog for v0.9.0 (2015-02-12)

[Plug] Plug.CSRFProtection utilise maintenant un cookie au lieu d'une session et Attend un paramètre "_csrf_token" au lieu de "csrf_token"

Pour accéder à la valeur du jeton, récupérez si du cookie, qui ressemble au côté serveur

Map.get(@conn.req_cookies, "_csrf_token")

Donc, pour votre code, ressemblerait à quelque chose comme

<h2>Edit Directory</h2>
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id, 'x-csrf-token': Map.get(@conn.req_cookies, "_csrf_token") %>" method="post">
  <div class="form-group">
    <label for="directory" class="col-sm-2 control-label">Directory</label>
    <div class="col-sm-10">
      <input type="hidden" name="_method" value="PATCH">
      <input type="text" class="form-control" value="<%= @directory.directory %>" name="directory" placeholder="Directory" required="required">
    </div>
  </div>

Maintenant, pour être complet, j'avais besoin de la CSRF mise à jour pour les requêtes construites uniquement du côté client. Voici comment j'ai accédé au cookie en javascript, à l'aide des cookies JQuery, pour un accès facile. Vous devriez pouvoir voir la valeur dans votre navigateur en lançant la commande suivante

$.cookie("_csrf_token")

Ce qui pourrait retourner quelque chose comme

"K9UDa23e1sacdadfmvu zzOD9VBHTSr1c/lcvWY="

Notez ci-dessus l’espace qui, dans phoenix, était encodé en url en +, ce qui causait toujours l’échec de la CSRF. Maintenant, c’est un bug dans Plug, ou tout simplement quelque chose à gérer, je ne suis pas sûr, donc pour l’instant je gère simplement le + explicitement

$.cookie("_csrf_token").replace(/\s/g, '+');

Avec l'accès au jeton CSRF, il ne nous reste plus qu'à ajouter le jeton x-csrf à l'en-tête de votre demande ( merci à vous ilake ). Voici le code pour le faire fonctionner avec un appel ajax (remplissez l'url, les données et la réponse en conséquence).

$.ajax({ 
  url: 'YOUR URL HERE',
  type: 'POST',
  beforeSend: function(xhr) {
    xhr.setRequestHeader('x-csrf-token', $.cookie("_csrf_token").replace(/\s/g, '+'))
  },
  data: 'someData=' + someData,
  success: function(response) {
    $('#someDiv').html(response);
  }
});

Notez que vous pouvez également renvoyer le paramètre _csrf_token, mais je préfère ce qui précède et cela me semble plus propre.

Note finale, je n'avais pas assez de points de réputation pour poster correctement un lien vers un cookie JQuery, mais il devrait être facile de google. 

3
a4word

Sur la version 0.13 de Phoenix, vous pouvez faire

<input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>">

car dans le fichier web/web.ex il y a une importation de cette fonction.

20
Tsuharesu

Autre solution disponible depuis la v0.10.0, vous pouvez laisser Phoenix injecter l’entrée CSRF à votre place.

Exemple du guide de mise à niveau :

<%= form_tag("/hello", method: :post) %>
... your form stuff. input with csrf value is created for you.
</form>

Cela produira la balise de formulaire et quelques balises de saisie, dont celle de _csrf_token. Les résultats ressembleront à ceci:

<form accept-charset="UTF-8" action="/hello" method="post">
    <input name="_csrf_token" value="[automatically-inserted token]" type="hidden">
    <input name="_utf8" value="✓" type="hidden">
</form>

form_tag docs : "pour les requêtes 'post', la balise de formulaire inclura automatiquement une balise d'entrée avec le nom _csrf_token."

8
Tayler

J'ai trouvé la réponse sur http://phoenix.thefirehoseproject.com . Vous devez créer une fonction pour obtenir le jeton csrf:

web/view.ex

def csrf_token(conn) do
  Plug.Conn.get_session(conn, :csrf_token)
end

Puis récupérez-le dans le modèle:

web/template/directory/edit.html.eex

<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id %>" method="post">
   <input type="hidden" name="csrf_token" value="<%= csrf_token(@conn) %>">

Et c'est tout!

1
Paul B

Dans mon cas, c’était la ligne plug :scrub_params à l’origine du problème. Après avoir commenté la ligne, cela a fonctionné. Mais vous devez vous assurer de résoudre le problème, car l'application ne serait pas sécurisée sans les scrub_params.

0
Pratik Khadloya

Ma solution est:

  • Importez Phoenix.Controller.get_csrf_token:0 dans MyModule.view:0 (sous apps/my_app_web/lib/my_app_web.ex):

    import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]

  • Ajouter un paramètre caché dans ma fiche:

    <input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>" />

0
Viet Tran