CSRF Double-Submit Token Middleware
Issue a random CSRF cookie on safe requests and require a matching header on unsafe methods using constant-time comparison.
Code
package main
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"net/http"
)
const (
csrfCookie = "csrf_token"
csrfHeader = "X-CSRF-Token"
)
func newToken() (string, error) {
buffer := make([]byte, 32)
if _, err := rand.Read(buffer); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(buffer), nil
}
func CSRFMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, cookieErr := r.Cookie(csrfCookie)
switch r.Method {
case http.MethodGet, http.MethodHead, http.MethodOptions:
if cookieErr != nil {
token, err := newToken()
if err != nil {
http.Error(w, "cannot issue csrf token", http.StatusInternalServerError)
return
}
http.SetCookie(w, &http.Cookie{
Name: csrfCookie,
Value: token,
Path: "/",
HttpOnly: false,
SameSite: http.SameSiteLaxMode,
Secure: r.TLS != nil,
})
}
next.ServeHTTP(w, r)
return
}
if cookieErr != nil {
http.Error(w, "missing csrf cookie", http.StatusForbidden)
return
}
header := r.Header.Get(csrfHeader)
if subtle.ConstantTimeCompare([]byte(header), []byte(cookie.Value)) != 1 {
http.Error(w, "invalid csrf token", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
handler := CSRFMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "accepted")
}))
http.Handle("/submit", handler)
_ = http.ListenAndServe(":8080", nil)
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.