vukem.DefaultMiddleware installs csrf.Middleware on the router stack. Nothing else is needed for forms or htmx in modern browsers.
How it works#
http.CrossOriginProtectionrejects cross-origin unsafe requests (POST,PUT,PATCH,DELETE) using theSec-Fetch-SiteandOriginheaders. Requests without either header (curl, server-to-server API clients) pass.- Rejections answer 403 through the error handler and log a
csrf.rejectedsecurity event. - The session cookie uses
SameSite=Laxas a second layer.
Configuration#
mw, err := csrf.Middleware(csrf.Config{
TrustedOrigins: []string{"https://admin.example.com"}, // other origins allowed to post
BypassPatterns: []string{"POST /webhooks/stripe"}, // ServeMux patterns exempt (webhooks)
TokenFallback: true, // see below
})
if err != nil {
return err // invalid origins or patterns fail at boot
}
r.Use(mw)With DefaultMiddleware, trusted origins come from CSRF_TRUSTED_ORIGINS (comma-separated).
Token fallback#
With TokenFallback, unsafe requests that carry neither Sec-Fetch-Site nor Origin but do carry a session cookie (old browsers) must present the session token, either in the _token form field or the X-CSRF-Token header. Comparison is constant-time. Register it after the session middleware.
Forms in the starter always include the field, so they keep working with the fallback on:
<form method="post" action="{{route "posts.store"}}">
{{csrfField}}
<input name="title">
<button>Save</button>
</form>| Helper | Output |
|---|---|
{{csrfField}} | <input type="hidden" name="_token" value="…"> |
{{csrfToken}} | The token string, e.g. for hx-headers |
csrf.Token(ctx) | The token in Go code ("" without a session) |