package authsupport import ( "crypto/rand" "crypto/subtle" "encoding/base64" "fmt" ) // csrfTokenBytes is the amount of entropy in a CSRF token before // encoding. This isn't a secret that needs to resist offline brute // force the way a password hash does — it just needs to be // unguessable to a third-party site, so 128 bits is comfortably enough. const csrfTokenBytes = 16 // NewCSRFToken returns a fresh, URL-safe random token suitable for the // double-submit-cookie pattern: the same value is set as a cookie and // embedded as a hidden form field, and a request is rejected unless the // two match (see VerifyCSRF). Unlike session/verification tokens, the // raw value itself is what's compared — there's no server-side lookup, // so no hash-at-rest step is needed here. func NewCSRFToken() (string, error) { b := make([]byte, csrfTokenBytes) if _, err := rand.Read(b); err != nil { return "", fmt.Errorf("auth: generate csrf token: %w", err) } return base64.RawURLEncoding.EncodeToString(b), nil } // VerifyCSRF reports whether a cookie value and a submitted form value // match, in constant time. Both empty is never valid. func VerifyCSRF(cookieValue, formValue string) bool { if cookieValue == "" || formValue == "" { return false } return subtle.ConstantTimeCompare([]byte(cookieValue), []byte(formValue)) == 1 }