2
votes

i'm trying to make local file upload, but when i do POST request, i got invalid CSRF (Cross Site Request Forgery) token, make sure all requests include a valid '_csrf_token' param or 'x-csrf-token' header error

i already try to call this function

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

my .html.eex :

<form action="/accounts/new/csv-validator" method="post" 
enctype="multipart/form-data">
    <input type="hidden" name="_csrf_token" value="<%= @get_csrf_token() %>">
    <input class="form-control input-bordered" id="user_photo" name="user[photo]" type="file">
</form>

expected result : success to do POST with valid csrf token

Phoenix Version : v1.3.4

2
In all honesty I don't think there's enough information here. The only suspect thing I see is that you have a method in your first snippet "csrf_token()", then in the input you have "@get_csrf_token()". - JustAnotherSoul
Try this name="csrf-token" id="csrf-token" value="<%= Plug.CSRFProtection.get_csrf_token() %>" - script

2 Answers

0
votes

In Phoenix @ is a macro, i.e. it's the name of a macro function:

@ is actually a macro that translates @key to Map.get(assigns, :key).

(https://hexdocs.pm/phoenix/templates.html)

I actually think there is an error in that quote, and it should read:

@ is actually a macro that translates @key to Keyword.get(assigns, :key).

And assigns comes from here:

render(conn, template, assigns)

https://hexdocs.pm/phoenix/Phoenix.Controller.html#render/3

As a result, if you call render like this:

render(conn, "page.html", message: "hello", answer: "yello")
                          |_______________________________|
                                        ^
                                        |
                                      assigns (a keyword list)

then in the page.html template if you write @message, then Phoenix will replace that with Keyword.get(assigns, :message), which evaluates to "hello".

That should demonstrate that writing @get_csrf_token() in your template is nonsense. Instead, what you need to do is something like this:

def your_action(conn, _params) do
  render(conn, "page.html", csrf: csrf_token(conn) )
end

Then, in your template you can write @csrf to insert the token.

0
votes

I found @7stud's educational and helpful, but I didn't leave this stack with a clear solution; so I thought I'd give it a try.

When rendering EEx templates with Phoenix, there's quite a few options. It seems @Leka isn't using Phoenix.HTML in this question, but I'll list it alongside other options.

Without Phoenix.HTML:

<form method="post" action"...">
  <input type="hidden" name="_csrf_token" value="<%= Plug.CSRFProtection.get_csrf_token() %>">
  ...
</form>

Phoenix's version does the same thing:

<form method="post" action"...">
  <input type="hidden" name="_csrf_token" value="<%= Phoenix.Controller.get_csrf_token() %>">
  ...
</form>

With Phoenix.HTML, the various forms of form_for do this automatically when they are POSTing (which is the default action):

<%= form_for @conn, "..." %>
  ...
</form>

I can't tell 100% from the question, but it looks like @Leka could be defining a custom CSRF function. If that is what's being done, calling that function from the template with the "@" macro will work as long as that function is defined in the corresponding Phoenix View file.

This approach is unnecessary unless custom code beyond what Plug provides is needed. Same thing with @7stud's approach using the assigns argument in render. Again, no disrespect toward @7stud's answer; it was probably a direct response to their interpretation of what @Leka was trying to do.