Skip to content

Commit 6171bec

Browse files
author
Codex
committed
fix: use hex CSRF tokens + add server-side debug logging
- Switch from base64 to hex tokens (no special chars that break cookie parsing) - Simpler JS cookie reader (no decodeURIComponent needed) - Add server-side log for CSRF rejections showing header vs cookie values - Verified all 6 POST endpoints pass CSRF validation
1 parent 2e9fbcb commit 6171bec

3 files changed

Lines changed: 15 additions & 8 deletions

File tree

internal/web/csrf.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package web
22

33
import (
44
"crypto/rand"
5-
"encoding/base64"
5+
"encoding/hex"
6+
"log"
67
"net/http"
78
)
89

@@ -35,6 +36,7 @@ func CSRFMiddleware(next http.Handler) http.Handler {
3536
// Mutation methods — validate token
3637
cookie, err := r.Cookie(csrfCookieName)
3738
if err != nil || cookie.Value == "" {
39+
log.Printf("CSRF reject %s %s: cookie missing (err=%v)", r.Method, r.URL.Path, err)
3840
http.Error(w, "CSRF cookie missing", http.StatusForbidden)
3941
return
4042
}
@@ -43,6 +45,7 @@ func CSRFMiddleware(next http.Handler) http.Handler {
4345
headerToken = r.FormValue("csrf_token")
4446
}
4547
if headerToken != cookie.Value {
48+
log.Printf("CSRF reject %s %s: token mismatch (header=%q cookie=%q)", r.Method, r.URL.Path, headerToken, cookie.Value)
4649
http.Error(w, "CSRF token mismatch", http.StatusForbidden)
4750
return
4851
}
@@ -64,5 +67,5 @@ func GetCSRFToken(r *http.Request) string {
6467
func generateCSRFToken() string {
6568
b := make([]byte, csrfTokenLength)
6669
rand.Read(b)
67-
return base64.URLEncoding.EncodeToString(b)
70+
return hex.EncodeToString(b)
6871
}

internal/web/templates/layout.templ

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ templ Layout(title string, csrfToken string, isManager bool, activePage string)
1414
<body>
1515
<script>
1616
document.body.addEventListener('htmx:configRequest', function(e) {
17-
// Read CSRF token from cookie (double-submit cookie pattern)
18-
var match = document.cookie.match(/(^|;\s*)_csrf=([^;]*)/);
19-
if (match) {
20-
e.detail.headers['X-CSRF-Token'] = decodeURIComponent(match[2]);
17+
// Read CSRF token from _csrf cookie (double-submit cookie pattern)
18+
var cookies = document.cookie.split(';');
19+
for (var i = 0; i < cookies.length; i++) {
20+
var c = cookies[i].trim();
21+
if (c.indexOf('_csrf=') === 0) {
22+
e.detail.headers['X-CSRF-Token'] = c.substring(6);
23+
break;
24+
}
2125
}
2226
});
2327
</script>

internal/web/templates/layout_templ.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)