Skip to content

Commit 48d7505

Browse files
cfsmp3claude
andauthored
security: validate OAuth state parameter to prevent CSRF attacks (#406)
Generate cryptographically secure random state per OAuth request and validate it in the callback to prevent cross-site request forgery. - Add generateOAuthState() using crypto/rand for secure random generation - Store state in session before redirect to OAuth provider - Validate state in callback matches stored value - Clear state after validation (one-time use) - Log warnings for state mismatches Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8b9ca56 commit 48d7505

1 file changed

Lines changed: 44 additions & 2 deletions

File tree

backend/controllers/app_handlers.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package controllers
33
import (
44
"ccsync_backend/utils"
55
"context"
6+
"crypto/rand"
7+
"encoding/base64"
68
"encoding/json"
79
"net/http"
810
"os"
@@ -11,6 +13,15 @@ import (
1113
"golang.org/x/oauth2"
1214
)
1315

16+
// generateOAuthState creates a cryptographically secure random state string
17+
func generateOAuthState() (string, error) {
18+
b := make([]byte, 32)
19+
if _, err := rand.Read(b); err != nil {
20+
return "", err
21+
}
22+
return base64.URLEncoding.EncodeToString(b), nil
23+
}
24+
1425
type App struct {
1526
Config *oauth2.Config
1627
SessionStore *sessions.CookieStore
@@ -26,9 +37,27 @@ type App struct {
2637
// @Accept json
2738
// @Produce json
2839
// @Success 307 {string} string "Redirect to OAuth provider"
40+
// @Failure 500 {string} string "Internal server error"
2941
// @Router /auth/oauth [get]
3042
func (a *App) OAuthHandler(w http.ResponseWriter, r *http.Request) {
31-
url := a.Config.AuthCodeURL("state", oauth2.AccessTypeOffline)
43+
// Generate a cryptographically secure random state to prevent CSRF attacks
44+
state, err := generateOAuthState()
45+
if err != nil {
46+
utils.Logger.Errorf("Failed to generate OAuth state: %v", err)
47+
http.Error(w, "Internal server error", http.StatusInternalServerError)
48+
return
49+
}
50+
51+
// Store state in session for validation in callback
52+
session, _ := a.SessionStore.Get(r, "session-name")
53+
session.Values["oauth_state"] = state
54+
if err := session.Save(r, w); err != nil {
55+
utils.Logger.Errorf("Failed to save OAuth state to session: %v", err)
56+
http.Error(w, "Internal server error", http.StatusInternalServerError)
57+
return
58+
}
59+
60+
url := a.Config.AuthCodeURL(state, oauth2.AccessTypeOffline)
3261
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
3362
}
3463

@@ -39,11 +68,25 @@ func (a *App) OAuthHandler(w http.ResponseWriter, r *http.Request) {
3968
// @Accept json
4069
// @Produce json
4170
// @Param code query string true "OAuth authorization code"
71+
// @Param state query string true "OAuth state parameter for CSRF protection"
4272
// @Success 303 {string} string "Redirect to frontend home page"
4373
// @Failure 400 {string} string "Bad request"
74+
// @Failure 403 {string} string "Invalid OAuth state"
4475
// @Failure 500 {string} string "Internal server error"
4576
// @Router /auth/callback [get]
4677
func (a *App) OAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
78+
// Validate OAuth state parameter to prevent CSRF attacks
79+
state := r.URL.Query().Get("state")
80+
session, _ := a.SessionStore.Get(r, "session-name")
81+
expectedState, ok := session.Values["oauth_state"].(string)
82+
if !ok || state == "" || state != expectedState {
83+
utils.Logger.Warnf("OAuth state mismatch: expected=%s, got=%s", expectedState, state)
84+
http.Error(w, "Invalid OAuth state", http.StatusForbidden)
85+
return
86+
}
87+
// Clear the state from session after validation (one-time use)
88+
delete(session.Values, "oauth_state")
89+
4790
code := r.URL.Query().Get("code")
4891

4992
t, err := a.Config.Exchange(context.Background(), code)
@@ -77,7 +120,6 @@ func (a *App) OAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
77120

78121
userInfo["uuid"] = uuidStr
79122
userInfo["encryption_secret"] = encryptionSecret
80-
session, _ := a.SessionStore.Get(r, "session-name")
81123
session.Values["user"] = userInfo
82124
if err := session.Save(r, w); err != nil {
83125
http.Error(w, err.Error(), http.StatusInternalServerError)

0 commit comments

Comments
 (0)