http-csrf-protection enforces CSRF for session-authenticated, state-changing
requests and issues tokens for rendering. Pure token functions live in
boundary.platform.core.csrf.
A state-changing request (POST/PUT/DELETE/PATCH) is validated — 403 on a missing or
invalid token — when CSRF is enabled, the path is not exempt, and the request is either
session-authenticated (session-token cookie / X-Session-Token header) or a /web
route. This covers /web, /web/admin, and any session-authenticated /api route.
Token-auth API clients that send no session cookie are not CSRF-vulnerable and are not
checked; safe methods (GET/HEAD/OPTIONS) and :exempt-paths are skipped.
;; resources/conf/<env>/config.edn under :active
:boundary/http
{:security
{:csrf {:enabled? true ; OPT-IN: lib default is false
:secret #or [#env CSRF_SECRET #env JWT_SECRET] ; defaults to JWT_SECRET
:exempt-paths ["/api/v1/payments/webhook"]}}} ; trailing /* = segment-prefix
Enforcement is opt-in: the library default is :enabled? false, so upgrading the
framework cannot start rejecting requests from a consumer that does not yet emit
tokens. An app turns it on with the block above (after emitting tokens in its /web
forms); the secret falls back to JWT_SECRET. Startup fails loud — the system wiring
throws and the app refuses to boot if CSRF is enabled with a blank secret, rather than
letting the interceptor fail open (run unvalidated).
Tokens are emitted with no per-handler wiring. For HTMX, either merge
(csrf/hx-headers) onto an element’s attributes (e.g. <body>) so all inherited
hx-* requests carry the X-CSRF-Token header, or rely on the shared page layout’s
<meta name="csrf-token"> tag plus the global htmx:configRequest listener that
attaches the header to every HTMX request. Plain (non-HTMX) forms include a hidden
field:
(require '[boundary.platform.core.csrf :as csrf])
[:form {:method "post" :action "/web/logout"}
(csrf/hidden-field) ; reads the token bound for the current request
[:button "Logout"]]
See the authentication guide for the request flow
and the unauthenticated (login) pre-session token.