InvalidCSRFToken error when attempting to use CSRFs "manually"

This incoming conn:

:plug_session => %{
  "_csrf_token" => "PHh4OxAsCycMPG0UDQwHMnQzIy85LwQwH24ZA_sogt4yfFum5GLBTNqJ",
  "session_cookie" => "zpu3CvCqVrzWiZJIGQ7/USVwL3jlbR3RzFSvhF3nTefFgSJoc142lxltxxRwBvpk4BI3pL7UcbkjZnaqayGYJw==",
  "user_id" => 5
},
...
req_headers: [
  ...
  {"x-csrf-token", "PHh4OxAsCycMPG0UDQwHMnQzIy85LwQwH24ZA_sogt4yfFum5GLBTNqJ"}
]

Gives a Plug.CSRFProtection.InvalidCSRFTokenError if the route is piped through the :protect_from_forgery plug.

Here’s the controller code that generates the CSRF:

  csrf_token = get_csrf_token()

  conn
  |> put_session("_csrf_token", csrf_token)
  |> put_resp_header("csrf_token", csrf_token)
  |> render(...)

The front-end (which is a SPA) then takes the CSRF from the response header and stores it, and includes it in subsequent POST requests. That’s my understanding of how CSRFs are meant to be used.

A few people on Slack tried to help troubleshoot but we didn’t get anywhere.

What conditions need to be met in order for a CSRF token to be considered “valid”? I dug into the code of the various Plug modules (all the way down to Plug.Conn.Crypto), and while I was able to somewhat reason about the flow, I wasn’t able to identify the problem because the functions return simply :error, without any detailed reasons/explanations.

I should note that I’m using a custom, Postgres-backed session store. Not sure if that’s relevant though.

It’s not really a SPA because You talk with html controllers, not json…

How do You load your SPA?

I have been using mix of HTML and React, and to pass data from Html to JS I have used data attribute.

This way I could pass a “manual” csrf token to SPA, and use it from a React form.

It is also possible to use an Html form, and use a JS component to manage a field, and return the value in a hidden input. I have done this to use a JS editor (Draft JS) for a rich text field, inside a Phoenix form.

It’s a single-page VueJS application that sends requests in JSON format, and those requests are piped through:

pipeline :api do
  plug :accepts, ["json"]
  plug :fetch_session
  plug :protect_from_forgery
end

Session with an api?!

Yes, it’s a SPA that uses cookie-based authentication.

I have never used Vue but it seems to be working like React, so You can exchange data with JS via data attribute of the anchor.

Fetching, storing and sending the CSRF is not the issue. I’m asking why Phoenix thinks the sent CSRF is invalid.

This is how I do it with a React form…

          <form 
            action={`/paintings/${id}/samples`}
            method="post" >
            <input type="hidden" value={token} name="_csrf_token"/>
            ...
            <button className="btn btn-sm btn-primary">Take Sample</button>
          </form>

and the form pass to Phoenix without problem.

Like I said, passing the CSRF to Phoenix is not the issue.

@egeersoz were you able to solve this? If yes then how?