2015-02-18 18 views
17

Ricevo un errore di token CSRF non valido durante il tentativo di aggiornare (o creare) un record. Sto usando Elixir v1.0.3, Erlang/OTP 17 [erts-6.3] e Phoenix v0.8.0 (penso, non sono sicuro di come controllare la versione di Phoenix). Sto creando un'app Web che segue principalmente le guide di Phoenix e le risorse di esempio del sito di vendita di elisir. Tuttavia, quando provo a pubblicare informazioni da un modulo html, ottengo l'errore del token CSRF non valido. Seguendo il consiglio dato nell'errore, ho aggiunto "x-csrf-token": csrf_token all'azione.Phoenix - Errore token CSRF (Cross Site Forgery Protection) non valido

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

ma ricevo il seguente errore:

[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 

Per quanto posso dire (essendo nuovo a Elixir, Phoenix, e HTML), " azione "è essenzialmente un percorso e tutti i parametri che inserisco troveranno la via per tornare all'applicazione. E, in effetti, trovo che x-csrf-token = "" è passato al router, quindi @csrf_token non deve essere corretto. Non sono sicuro esattamente da dove viene il csrf_token, quindi non so come fare riferimento (o forse sto facendo questo completamente sbagliato).

Qualsiasi idea sarebbe molto apprezzata.

risposta

3

Per vedere la versione installata, eseguire

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

che mostra che Phoenix si ha nella directory dipendenze.

Inoltre, se/quando si esegue l'aggiornamento a phoenix 0.9.0, le cose sono cambiate (a causa di aggiornamenti a Plug.CSRFProtection), il CSRF funziona in modo diverso utilizzando i cookie anziché le sessioni.

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

[Plug] Plug.CSRFProtection now uses a cookie instead of session and expects a "_csrf_token" parameter instead of "csrf_token"

Per accedere il valore del gettone, afferrare se dal cookie, che sul lato server sembra

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

Così, per il codice, sarebbe simile

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

Ora, per completezza, avevo bisogno del CSRF aggiornato per requ ests costruito lato puramente client, ecco come ho avuto accesso al cookie in javascript, utilizzando i cookie JQuery, per un facile accesso.Si dovrebbe essere in grado di vedere il valore nel tuo browser eseguendo il seguente

$.cookie("_csrf_token") 

Quale potrebbe restituire qualcosa come

"K9UDa23e1sacdadfmvu zzOD9VBHTSr1c/lcvWY=" 

Nota in precedenza, lo spazio, che a Phoenix è stato essere URL codificato per +, che stava ancora causando il fallimento del CSRF. Ora è che un bug in Plug, o semplicemente qualcosa da gestire, non sono sicuro, quindi per ora mi sto semplicemente la manipolazione del + esplicitamente

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

Con l'accesso al token CSRF, ora solo bisogno di aggiungere il token x-csrf all'intestazione della richiesta (thank you ilake). Ecco il codice per farlo funzionare con una chiamata ajax (compilare l'url e i dati e la risposta di conseguenza).

$.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); 
    } 
}); 

Nota che si potrebbe rispedire il _csrf_token come parametro di così, ma io preferisco il sopra e ci si sente più pulito per me.

Nota finale, non avevo abbastanza punti reputazione per pubblicare correttamente un link al cookie jquery, ma dovrebbe essere facile da google.

+0

Grazie per la risposta dettagliata. Si scopre che sto eseguendo Phoenix 0.8.0, quindi il codice sotto funziona, ma mi hai salvato un grosso mal di testa quando eseguo l'aggiornamento a 0.9.0! –

+0

Grazie ancora @ a4word, ho eseguito l'aggiornamento a Phoenix 0.9.0 e ho modificato il modello per ottenere il token dal cookie. Tuttavia, stranamente, sembra funzionare, ma sto ancora ricevendo un errore di token CSRF (Cross Site Forgery Protection) non valido. Il token è 'ne0GATpoc/EW6jbIbC7tmfkAWl4qb1opTPWmmfYFTRY =' (senza spazi) e il template crea un url 'POST/config/directory/9? X-csrf-token = ne0GATpoc% 2FEW6jbIbC7tmfkAWl4qb1opTPWmmfYFTRY% 3D' Non ho idea del perché Plug.CSRFProtection non mi piace. Sai o dovrei aprire un'altra domanda? –

+0

Ho cambiato il nome del token nel modello da: ''x-csrf-token': Map.get (@ conn.req_cookies," _csrf_token ")' a: ''_csrf_token': Map.get (@ conn.req_cookies , "_csrf_token") e ora funziona –

1

Ho trovato la risposta su http://phoenix.thefirehoseproject.com. È necessario creare una funzione per ottenere il token CSRF:

web/view.ex

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

Poi recuperarlo nel modello:

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) %>"> 

E questo è tutto!

19

Sulla versione 0.13 di Phoenix si può fare

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

perché in archivio web/web.ex c'è un'importazione di questa funzione.

+0

Grazie! Stavo solo aggiornando a 0.13. –

+0

Grazie-- questa è l'unica modifica al Web/web.ex generato dalla v0.12 -> 0.13 – Jay

6

Come un'altra soluzione disponibile dalla versione 10.0.0, è possibile consentire a Phoenix di iniettare l'input CSRF.

Example from upgrade guide:

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

che sarà in uscita il tag form e qualche tag input, tra cui il _csrf_token uno. I risultati saranno simile a questa:

<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: "per le richieste 'post', il tag form include automaticamente un tag input con il nome _csrf_token"

0

Nel mio caso è stata la linea plug :scrub_params a causare il problema. Dopo aver commentato la linea, ha funzionato. Ma è necessario assicurarsi di risolvere il problema in quanto l'app sarebbe insicura senza scrub_params.