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.

Original source