CSRF¶
Cross-Site Request Forgery protection using gorilla/csrf.
Package: github.com/oliverandrich/burrow/contrib/csrf
Depends on: none
Setup¶
The CSRF app provides middleware that protects POST/PUT/DELETE/PATCH requests using the double-submit cookie pattern. GET, HEAD, OPTIONS, and TRACE requests pass through without validation.
How It Works¶
- On every request, the middleware sets a CSRF cookie and generates a masked token
- The token is available in templates via the
{{ csrfToken }}function (provided byHasRequestFuncMap) - Templates include the token in forms as a hidden field
- On unsafe requests (POST, PUT, DELETE, PATCH), the middleware validates the submitted token against the cookie
Using Tokens in Templates¶
The CSRF app implements HasRequestFuncMap and provides two template functions:
| Function | Returns | Description |
|---|---|---|
{{ csrfToken }} |
string |
The raw CSRF token value |
{{ csrfField }} |
template.HTML |
A complete <input type="hidden"> element |
Use csrfField for the common case — it renders the entire hidden input:
{{ define "notes/create" -}}
<form method="POST" action="/notes">
{{ csrfField }}
<input type="text" name="title" placeholder="Title">
<button type="submit">Create</button>
</form>
{{- end }}
Use csrfToken when you need just the token value, e.g. for meta tags or JavaScript.
htmx¶
The admin layout and example/notes's app/layout set {{ csrfHxHeaders }} on the <body> tag, so all htmx requests inside those layouts include the CSRF token automatically.
If you use a custom layout, add the csrfHxHeaders function to your <body> tag:
This renders hx-headers='{"X-CSRF-Token":"..."}' when the csrf app is registered, or nothing when it is not — keeping the HTML clean.
fetch / XMLHttpRequest¶
Include a meta tag in your layout so JavaScript can read the token from the DOM:
fetch("/api/submit", {
method: "POST",
headers: {
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content,
},
body: JSON.stringify(data),
});
Go API¶
The token is also available in Go code via the context:
Configuration¶
| Flag | Env Var | Default | Description |
|---|---|---|---|
--csrf-key |
CSRF_KEY |
auto-generated | 32-byte hex auth key |
CSRF Key
If no key is provided, one is auto-generated and logged to stdout. Tokens will not persist across server restarts. For production, always set CSRF_KEY.
Generate a key:
Exempting Webhook Paths¶
Cross-origin webhook receivers — Webmention inbound, ActivityPub inbox, payment callbacks — accept POSTs from external services without a CSRF token by design. Bearer-authenticated JSON CRUD APIs need the same exemption (they carry no cookie). The csrf app honours a capability interface so the app that owns the route declares the exemption locally:
package webmention
import (
"github.com/oliverandrich/burrow/contrib/csrf"
)
// Compile-time check.
var _ csrf.ExemptPaths = (*App)(nil)
func (a *App) CSRFExemptPaths() []string {
return []string{"/webmention"}
}
At boot the csrf app walks the registry, collects every CSRFExemptPaths() return value, and builds a single matcher. No coordination from main.go — adding a new webhook receiver only requires implementing the interface on its app.
Pattern syntax (minimal by design):
"/webmention"— exact match (matches/webmention, not/webmention/x)."/inbox/"— prefix match (trailing slash; matches/inbox/alice,/inbox/bob/feed, not/inbox).
No glob or chi-style placeholders. Apps with more complex routing constraints should list each path explicitly or front a prefix-matched subspace and gate further inside their handler.
Use sparingly
Each exempt path is a hole in CSRF protection. Limit them to endpoints that legitimately accept off-domain POSTs and validate authenticity by other means (HTTP signatures for Webmention, signed payloads for payment webhooks, etc.).
Cookie Properties¶
HttpOnly: true— not accessible from JavaScriptSecure— set automatically when base URL is HTTPSSameSite: Lax— prevents cross-site cookie sendingPath: /— available on all routes
Interfaces Implemented¶
| Interface | Description |
|---|---|
burrow.App |
Required: Name() |
Configurable |
CLI flag for auth key |
HasMiddleware |
CSRF protection middleware |
HasRequestFuncMap |
Provides csrfToken and csrfField functions to templates |