Skip to content

The basics

CSRF protection

Cross-site request forgery protection with Go's CrossOriginProtection, plus an optional session token fallback.

Last updated:

vukem.DefaultMiddleware installs csrf.Middleware on the router stack. Nothing else is needed for forms or htmx in modern browsers.

How it works#

  • http.CrossOriginProtection rejects cross-origin unsafe requests (POST, PUT, PATCH, DELETE) using the Sec-Fetch-Site and Origin headers. Requests without either header (curl, server-to-server API clients) pass.
  • Rejections answer 403 through the error handler and log a csrf.rejected security event.
  • The session cookie uses SameSite=Lax as a second layer.

Configuration#

Go
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:

resources/views/posts/create.html
<form method="post" action="{{route "posts.store"}}">
	{{csrfField}}
	<input name="title">
	<button>Save</button>
</form>
HelperOutput
{{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)