38 lines
1.3 KiB
Go
38 lines
1.3 KiB
Go
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
|
|
}
|