3

I'm currently learning Elixir by trying to make a small Plug project. Most of it goes fine except for sessions and CSRF protection. When I make GET requests, I see no session cookies in Firefox or HTTPie, and when I make POST requests, I get a 500 error (but the logger is silent).

Here's my current router code:

defmodule ElxSimpleApi.Web do
  require Logger

  use Plug.Router
  import Plug.Conn

  alias ElxSimpleApi.{Models, Repo}

  plug Plug.Logger, log: :debug
  plug Plug.Parsers, parsers: [:urlencoded, :json],
    pass: ["text/*", "application/json"],
    json_decoder: Poison

  plug :put_secret_key_base

  plug Plug.Session, store: :cookie,
    key: "_elx_simple_api_session",
    encryption_salt: "elxsimpleapienc",
    signing_salt: "elxsimpleapisign",
    log: :debug
  plug :fetch_session
  plug Plug.CSRFProtection


  plug :match
  plug :dispatch

  # A bunch of routes here, omitted for clarity

  match _ do
    send_resp(conn, 404, "oops")
  end

  defp fetch_person(:int, id), do: Models.Person |> Repo.get(id)
  defp fetch_person(:str, sid), do: fetch_person(:int, String.to_integer(sid))

  defp ecto_to_map(struct) do
    struct |> Map.from_struct |> Map.drop([:__meta__])
  end

  defp put_secret_key_base(conn, _) do
    put_in conn.secret_key_base, "d5b2hHZGsUfcYB8lImcxooaLfVBlB5bg/z9a99jjHuXTvt7yb5neykHrYEjuNFnD"
  end
end

Please tell me what I'm doing wrong. Thank you!

Update: Thanks to @josé-valim's advice, I now know that the 500 error is due to the invalid CSRF token. But the cookie still isn't being set.

art-solopov
  • 4,289
  • 3
  • 25
  • 44
  • 1
    Try adding `use Plug.Debugger` right after `use Plug.Router` and see if adding the debugger will give you proper stacktrace, logging and everything. – José Valim Dec 19 '17 at 22:18

3 Answers3

3

Apparently, the problem was connected to this issue: Plug.CSRFProtection doesn't automatically put the CSRF token in session, and Plug.Session doesn't actually create a session until something is put in it.

I had to add this plug (right after plug Plug.CSRFProtection):

defp put_csrf_token_in_session(conn, _) do
  Plug.CSRFProtection.get_csrf_token
  conn |> put_session("_csrf_token", Process.get(:plug_unmasked_csrf_token))
end
art-solopov
  • 4,289
  • 3
  • 25
  • 44
  • 1
    Glad you figured it out. If there is anyway to improve the docs, please send a PR! – José Valim Dec 21 '17 at 07:37
  • To be honest, I think Plug.CSRFProtection itself could be improved in this regard. I'll try to make a PR when I have the time, though I'm very new to Elixir. – art-solopov Dec 21 '17 at 08:33
1

If you want to get csrf_token it is better to use Plug.CSRFProtection.get_csrf_token() instead of hitting Process.get directly.

Currently, if csrf_protection(protect_from_forgery) enabled it will set token to the session field "_csrf_token" by default. Name of the field can be configured.

If you make your own session store(@behaviour Plug.Session.Store) and you want to have csrf_protection work you need to handle "_csrf_token" in your custom session store yourself.

Niv-Mizzet
  • 361
  • 3
  • 6
0

I also add a plug like

defp put_csrf_token_in_session(conn, _) do
    conn
    |> Plug.Conn.put_req_header("x-csrf-token", Plug.CSRFProtection.get_csrf_token)
    |> put_session("_csrf_token", Process.get(:plug_unmasked_csrf_token))
  end
Chagit
  • 1
  • 1