an updated and hopefully faster version of the ST Toolbox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

52 lines
1.7 KiB

package middleware
import (
"crypto/rand"
"encoding/base64"
"net/http"
"strings"
"time"
)
// CSRFSimple is a lightweight double-submit-cookie CSRF middleware suitable for HTMX
// - Sets a readable XSRF-TOKEN cookie on safe requests if missing
// - Requires unsafe requests to include X-CSRF-Token header matching the cookie
func CSRFSimple(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
method := strings.ToUpper(r.Method)
// Determine if connection is effectively HTTPS (behind proxy aware)
isHTTPS := r.TLS != nil || strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https")
// Ensure token cookie exists for safe methods
if method == http.MethodGet || method == http.MethodHead || method == http.MethodOptions || method == http.MethodTrace {
if _, err := r.Cookie("XSRF-TOKEN"); err != nil {
// Generate a random token
buf := make([]byte, 32)
if _, err := rand.Read(buf); err == nil {
token := base64.RawURLEncoding.EncodeToString(buf)
http.SetCookie(w, &http.Cookie{
Name: "XSRF-TOKEN",
Value: token,
Path: "/",
HttpOnly: false, // must be readable by client script
Secure: isHTTPS,
SameSite: http.SameSiteLaxMode,
Expires: time.Now().Add(12 * time.Hour),
})
}
}
next.ServeHTTP(w, r)
return
}
// For unsafe methods, require header matches cookie
headerToken := r.Header.Get("X-CSRF-Token")
cookie, err := r.Cookie("XSRF-TOKEN")
if err != nil || headerToken == "" || cookie == nil || cookie.Value == "" || cookie.Value != headerToken {
http.Error(w, "Forbidden - CSRF", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}