Phoenix - Invalid CSRF (Cross Site Forgery Protection) token error
elixir, phoenix-framework
Solution
To see the version installed, run
cat ./deps/phoenix/mix.exs | grep version
Which shows you which phoenix you have in the deps directory.
Also, if/when you upgrade to phoenix 0.9.0, things have changed (due to updates to Plug.CSRFProtection), the CSRF works differently using cookies instead of sessions.
From 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"
To access the token's value, grab if from the cookie, which on the server side looks like
Map.get(@conn.req_cookies, "_csrf_token")
So for your code, would look something like
<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>
Now, for completeness, I needed the updated CSRF for requests built purely client side, so here is how I accessed the cookie within javascript, using JQuery cookies, for easy access. You should be able to see the value in your browser by running the following
$.cookie("_csrf_token")
Which might return something like
"K9UDa23e1sacdadfmvu zzOD9VBHTSr1c/lcvWY="
Note in the above, the space, which in phoenix was being url encoded to +, which was still causing the CSRF to fail. Now is that a bug in Plug, or simply something to be handled, I am not sure, so for now I am simply handling the + explicitly
$.cookie("_csrf_token").replace(/\s/g, '+');
With access to the CSRF token, we now just need to add the x-csrf-token to your request header (thank you ilake). Here is the code to make it work with an ajax call work (fill in the url and data and response accordingly).
$.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);
}
});
Note that you could send back the _csrf_token as parameter as well, but I prefer the above and it feels cleaner to me.
Final note, I didn't have enough reputation points to properly post a link to jquery cookie, but it should be easy to google.
Problem
I am receiving an Invalid CSRF token error when trying to update (or create) a record. I am using Elixir v1.0.3, Erlang/OTP 17 [erts-6.3], and Phoenix v0.8.0 (I think, I am not sure how to check Phoenix's version). I am creating a web app mostly following the Phoenix guides and the Elixir Dose Jobsite Example resources. However, when I try to post information from an html form, I get the Invalid CSRF token error. Following the advice given in the error, I added 'x-csrf-token': csrf_token to the 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> ... ``` but I receive the following error: ``` [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 ``` As far as I can tell (being new to Elixir, Phoenix, and HTML), "action" is essentially a path and any parameters I place in it will find their way back to the application. And, indeed, I find that x-csrf-token = "" is passed back to the router, so @csrf_token must not be correct. I am not sure exactly where the csrf_token comes from, so I do not know how to reference it (or perhaps I am doing this completely wrong). Any ideas would be greatly appreciated.