Skip to content

Commit 5401550

Browse files
Replace configurable CSRF with automatic pipeline separation
This commit replaces the disable_csrf configuration option with an automatic pipeline separation approach that resolves CSRF conflicts with Phoenix applications while maintaining security. Changes: - Remove disable_csrf option and related conditional logic - Implement automatic request routing by path: * /assets/* → Asset pipeline (no CSRF protection) * All other routes → Form pipeline (full CSRF protection) - Maintain CSRF tokens in HTML pages for JavaScript AJAX requests - Update documentation to explain the automatic approach Benefits: - No configuration required - works automatically - Fixes InvalidCrossOriginRequestError for JavaScript/CSS assets - Maintains security for all form submissions and API calls - Follows Phoenix pattern of separating assets from interactive routes This resolves conflicts when embedding in Phoenix applications while providing better security than fully disabling CSRF protection.
1 parent d295ad6 commit 5401550

File tree

1 file changed

+34
-40
lines changed

1 file changed

+34
-40
lines changed

lib/fun_with_flags/ui/router.ex

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@ defmodule FunWithFlags.UI.Router do
22
@moduledoc """
33
A `Plug.Router`. This module is meant to be plugged into host applications.
44
5+
## CSRF Protection
6+
7+
This router uses automatic pipeline separation to handle CSRF protection:
8+
9+
* **Static assets** (`/assets/*`) - Served directly without CSRF protection
10+
* **Interactive routes** (forms, API endpoints) - Full CSRF protection applied
11+
* **HTML pages** - Include CSRF tokens for JavaScript to use in AJAX requests
12+
13+
This approach resolves conflicts with Phoenix applications while maintaining security
14+
for form submissions and API calls.
15+
516
## Options
617
718
* `:namespace` - Path prefix for internal redirects. Use when the UI is mounted
819
under a sub-path that differs from the forward path (default: `""`)
9-
* `:disable_csrf` - Disable CSRF protection when the host application
10-
provides its own CSRF protection (default: `false`)
1120
1221
## Usage
1322
@@ -17,9 +26,6 @@ defmodule FunWithFlags.UI.Router do
1726
# With namespace - forward at /admin but UI redirects go to /admin/feature-flags/*
1827
forward "/admin", FunWithFlags.UI.Router, namespace: "feature-flags"
1928
20-
# Disable CSRF when host app provides protection
21-
forward "/feature-flags", FunWithFlags.UI.Router, disable_csrf: true
22-
2329
See the [Readme](/fun_with_flags_ui/readme.html#how-to-run) for more detailed instructions.
2430
"""
2531
require Logger
@@ -32,26 +38,13 @@ defmodule FunWithFlags.UI.Router do
3238

3339
plug Plug.Logger, log: :debug
3440

35-
plug Plug.Static,
36-
gzip: true,
37-
at: "/assets",
38-
from: :fun_with_flags_ui
39-
40-
plug :maybe_protect_from_forgery, Plug.CSRFProtection.init([])
41-
plug :maybe_assign_csrf_token
42-
43-
plug Plug.Parsers, parsers: [:urlencoded]
44-
plug Plug.MethodOverride
45-
41+
plug :route_by_type
4642
plug :match
4743
plug :dispatch
4844

4945
@doc false
5046
def call(conn, opts) do
51-
conn =
52-
conn
53-
|> extract_namespace(opts)
54-
|> extract_csrf_config(opts)
47+
conn = extract_namespace(conn, opts)
5548
super(conn, opts)
5649
end
5750

@@ -308,32 +301,33 @@ defmodule FunWithFlags.UI.Router do
308301
end
309302

310303

311-
defp extract_csrf_config(conn, opts) do
312-
disable_csrf = opts[:disable_csrf] || false
313-
Plug.Conn.put_private(conn, :fun_with_flags_disable_csrf, disable_csrf)
314-
end
315-
316-
317-
defp csrf_disabled?(conn) do
318-
conn.private[:fun_with_flags_disable_csrf] == true
304+
defp route_by_type(conn, _opts) do
305+
case conn.request_path do
306+
"/assets" <> _ ->
307+
asset_pipeline(conn)
308+
_ ->
309+
form_pipeline(conn)
310+
end
319311
end
320312

321313

322-
defp maybe_protect_from_forgery(conn, opts) do
323-
if csrf_disabled?(conn) do
324-
conn
325-
else
326-
protect_from_forgery(conn, opts)
327-
end
314+
defp asset_pipeline(conn) do
315+
# Assets pipeline: only serve static files, no CSRF protection
316+
Plug.Static.call(conn,
317+
gzip: true,
318+
at: "/assets",
319+
from: :fun_with_flags_ui
320+
)
328321
end
329322

330323

331-
defp maybe_assign_csrf_token(conn, opts) do
332-
if csrf_disabled?(conn) do
333-
conn
334-
else
335-
assign_csrf_token(conn, opts)
336-
end
324+
defp form_pipeline(conn) do
325+
# Form pipeline: full CSRF protection for interactive routes
326+
conn
327+
|> protect_from_forgery(Plug.CSRFProtection.init([]))
328+
|> assign_csrf_token([])
329+
|> Plug.Parsers.call(parsers: [:urlencoded])
330+
|> Plug.MethodOverride.call([])
337331
end
338332

339333

0 commit comments

Comments
 (0)