I am implementing a session based authentication for a remix js framework (ssr framework) and it works initially not until I add csrf protection which results to invalid CSRF token
.
I have this router
pipeline :api do
plug :accepts, ["json"]
# plug :fetch_session
plug MonoWeb.Plugs.TestPlug
plug :protect_from_forgery
plug :put_secure_browser_headers
end
scope "/api", MonoWeb do
pipe_through :api
get "/csrf_token", CSRFTokenController, :index
post "/users/log_in", UserSessionController, :create
end
CSRFTokenController
def index(conn, _params) do
conn
|> put_resp_cookie("_csrf_token", get_csrf_token())
|> send_resp(204, "")
end
The auth flow with CSRF protection is like this
- call
/csrf_token
endpoint to set the csrf token in the cookie - frontend parsed the response cookie
- get the csrf token from parsed cookie and send it as part of request header for call to
/users/log_in
const csrfResponse = await fetch("http://localhost:4000/api/csrf_token");
const csrfCookies = await getCookiesFromResponse(csrfResponse);
const response = await fetch("http://localhost:4000/api/users/log_in", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-csrf-token": csrfCookies["_csrf_token"],
},
body: JSON.stringify({
user: {
email: "test@test.com",
password: "tester123456",
},
}),
credentials: "include",
});
I expect this one to work since I log the whole conn
struct in a custom plug right before the :protect_from_forgery
plug and the X-CSRF-TOKEN
exists there.
%Plug.Conn{
...
private: %{
MonoWeb.Router => [],
:before_send => [#Function<0.84243074/1 in Plug.Session.before_send/2>,
#Function<0.11807388/1 in Plug.Telemetry.call/2>],
:phoenix_endpoint => MonoWeb.Endpoint,
:phoenix_format => "json",
:phoenix_request_logger => {"request_logger", "request_logger"},
:phoenix_router => MonoWeb.Router,
:plug_session => %{},
:plug_session_fetch => :done
},
...
req_headers: [
{"accept", "*/*"},
{"connection", "close"},
{"content-length", "60"},
{"content-type", "application/json"},
{"host", "localhost:4000"},
{"user-agent", "node-fetch"},
{"x-csrf-token", "CVwCI2A9PGEtLB0ZHEV2Lh0bLC8JCXsry5HR1nt3UnYHK0Ok-JNcjqKe"}
],
...
}
but go this error instead.
[info] Sent 403 in 10ms
[debug] ** (Plug.CSRFProtection.InvalidCSRFTokenError) invalid CSRF (Cross Site Request Forgery) token, please make sure that:
* The session cookie is being sent and session is loaded
* The request include a valid '_csrf_token' param or 'x-csrf-token' header
(plug 1.14.2) lib/plug/csrf_protection.ex:316: Plug.CSRFProtection.call/2
(mono 0.1.0) MonoWeb.Router.api/2
(mono 0.1.0) lib/mono_web/router.ex:1: MonoWeb.Router.__pipe_through0__/1
(phoenix 1.7.6) lib/phoenix/router.ex:421: Phoenix.Router.__call__/5
(mono 0.1.0) lib/mono_web/endpoint.ex:1: MonoWeb.Endpoint.plug_builder_call/2
(mono 0.1.0) lib/plug/debugger.ex:136: MonoWeb.Endpoint."call (overridable 3)"/2
(mono 0.1.0) lib/mono_web/endpoint.ex:1: MonoWeb.Endpoint.call/2
(phoenix 1.7.6) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
(cowboy 2.10.0) /home/hei/recode/mono/server/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.10.0) /home/hei/recode/mono/server/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.10.0) /home/hei/recode/mono/server/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 4.1.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3