package app import ( "context" "crypto/subtle" "net/http" ) const csrfCookie = "spread_csrf" // csrf implements double-submit-cookie CSRF protection: a random token is kept // in an httponly cookie and echoed into every form; unsafe requests must send a // matching csrf_token field. Combined with the SameSite=Lax session/csrf // cookies, this blocks cross-site state changes. func (a *App) csrf(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tok := "" if c, err := r.Cookie(csrfCookie); err == nil { tok = c.Value } if tok == "" { tok = randomToken() http.SetCookie(w, &http.Cookie{ Name: csrfCookie, Value: tok, Path: "/", HttpOnly: true, Secure: a.secureCookie, SameSite: http.SameSiteLaxMode, }) } switch r.Method { case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete: if subtle.ConstantTimeCompare([]byte(r.PostFormValue("csrf_token")), []byte(tok)) != 1 { http.Error(w, "invalid or missing CSRF token", http.StatusForbidden) return } } next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), csrfKey, tok))) }) } func csrfToken(r *http.Request) string { t, _ := r.Context().Value(csrfKey).(string) return t }