Renames a CSRF body param to _csrf_token so that Plug.CSRFProtection
can find it.
Why this exists
Phoenix expects the CSRF token in conn.body_params["_csrf_token"] (or
the x-csrf-token header). Datastar sends its signals with every
request, but _-prefixed signal keys are front-end-only — never sent to
the backend — so a token named _csrf_token would never arrive.
The shim: expose the token as a non-prefixed signal (default csrf).
Because it is not _-prefixed, Datastar includes it in every request.
This plug then copies that value into _csrf_token in body_params
before Plug.CSRFProtection runs.
How the token travels (Datastar v1.0)
Datastar transports signals differently depending on the HTTP method:
- POST / PUT / PATCH — signals are sent as the JSON request body, so
the token arrives as a top-level body param (e.g.
body_params["csrf"]). - GET / DELETE — signals are serialized into the
datastarURL query parameter as a JSON object (e.g.?datastar={"csrf":"..."}), because these methods carry no body. This plug decodes that parameter and extracts the token from it, so DELETE requests — whichPlug.CSRFProtectiondoes check — pass validation too.
The plug checks both channels, so one setup covers page event() POSTs,
stream connect() POSTs, component events, and all verb helpers.
Security note
Because the token is a non-prefixed signal, it rides along on every
request — and on GET/DELETE that means the URL query string, not the
body. The token is still validated cryptographically against the
session-derived value by Plug.CSRFProtection, so this does not weaken
forgery protection. But a per-session credential sitting in URLs ends up
in access/proxy logs and same-origin Referer headers. If that matters
for your threat model, see the README's CSRF section for hardening
options (log scrubbing, Referrer-Policy, header-based transport).
Usage
# In your Phoenix router (before :protect_from_forgery):
plug Dstar.Plugs.RenameCsrfParam
# With a custom source param name:
plug Dstar.Plugs.RenameCsrfParam, from: "my_token"
# With a custom Datastar query param name (default "datastar"):
plug Dstar.Plugs.RenameCsrfParam, datastar_param: "ds"Options
:from— Source param name to copy from. Default:"csrf".:datastar_param— Name of the Datastar query parameter that carries the signals JSON on GET/DELETE requests. Default:"datastar".