package auth import ( "crypto/subtle" "net/http" ) // CSRFFieldName ist der Name des versteckten Formularfelds in den Templates. const CSRFFieldName = "csrf_token" // CSRFHeaderName erlaubt es, das Token bei einem fetch()-Aufruf im Header statt // im Formular zu schicken. const CSRFHeaderName = "X-CSRF-Token" // CSRFToken berechnet das Token zu einer Sitzung. // // # Wogegen schützt das? // // CSRF ("Cross-Site Request Forgery") nutzt aus, dass der Browser Cookies // automatisch mitschickt. Eine fremde Seite kann ein Formular auf unseren Server // abschicken lassen, und für den Server sieht es aus wie eine Aktion des // angemeldeten Nutzers: // //
// // // Das Token unterbindet das: Die fremde Seite kennt es nicht und kann es wegen // der Same-Origin-Policy des Browsers auch nicht auslesen. // // Das Präfix "csrf:" sorgt für Domänentrennung — dasselbe Geheimnis signiert auch // Session-Cookies, und eine Signatur aus dem einen Zusammenhang darf im anderen // nicht gelten. func (s *Signer) CSRFToken(subject string) string { return s.sign("csrf:" + subject) } // ValidCSRFToken prüft ein übermitteltes Token gegen die Sitzung. func (s *Signer) ValidCSRFToken(token, subject string) bool { expected := s.CSRFToken(subject) return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1 } // CSRFTokenFromRequest holt das Token aus dem Formular oder dem Header. // SignHint signiert eine Erfolgsmeldung, die über einen Redirect in der // Adresszeile mitreist (siehe withHint in internal/web). Ohne Signatur ließe // sich mit einem präparierten Link ein beliebiger Text als Meldung in die // Oberfläche schmuggeln — etwa „Bitte den Key an … schicken". // // Das Präfix "hint:" trennt diese Signaturen von Sitzungen und CSRF-Tokens: // Eine signierte Meldung taugt nie als gültige Sitzung oder umgekehrt. func (s *Signer) SignHint(message string) string { return s.sign("hint:" + message) } // ValidHint prüft, ob signature zu message passt — in konstanter Zeit, wie bei // den anderen Vergleichen mit Geheimnissen. func (s *Signer) ValidHint(message, signature string) bool { return subtle.ConstantTimeCompare([]byte(signature), []byte(s.SignHint(message))) == 1 } func CSRFTokenFromRequest(r *http.Request) string { if token := r.Header.Get(CSRFHeaderName); token != "" { return token } return r.FormValue(CSRFFieldName) }