diff --git a/apps/web/main.go b/apps/web/main.go index ff13d10..e5bb532 100644 --- a/apps/web/main.go +++ b/apps/web/main.go @@ -119,6 +119,7 @@ func main() { protected.Use(middleware.CSRFSimple) } else { protected.Use(csrfMw) + protected.Use(middleware.CSRFExposeToken) } } diff --git a/internal/middleware/csrf_expose.go b/internal/middleware/csrf_expose.go new file mode 100644 index 0000000..a5f0125 --- /dev/null +++ b/internal/middleware/csrf_expose.go @@ -0,0 +1,30 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/gorilla/csrf" +) + +// CSRFExposeToken sets a readable cookie with the per-request masked CSRF token on safe methods. +func CSRFExposeToken(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet, http.MethodHead, http.MethodOptions: + token := csrf.Token(r) + if token != "" { + secure := r.TLS != nil || strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https") + http.SetCookie(w, &http.Cookie{ + Name: "XSRF-TOKEN-VALUE", + Value: token, + Path: "/", + HttpOnly: false, + Secure: secure, + SameSite: http.SameSiteLaxMode, + }) + } + } + next.ServeHTTP(w, r) + }) +} diff --git a/templates/layout.html b/templates/layout.html index 9062a06..4d07857 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -7,13 +7,16 @@