Remove dummy user_id from OAuth state sessions

Use a simple signed cookie for OAuth state instead of PGStore,
which required a dummy user_id placeholder to satisfy the session
store's save logic.

Fixes #24
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthew Knight 2026-02-17 16:05:33 -08:00
parent e3ef03ddcd
commit c24f712cb6
No known key found for this signature in database
1 changed files with 36 additions and 18 deletions

View File

@ -53,12 +53,16 @@ func (h *OAuthHandler) Login(c *gin.Context) {
}
state := generateState()
session, _ := h.deps.SessionStore.Get(c.Request, "oauth_state")
session.Values["state"] = state
session.Values["user_id"] = "00000000-0000-0000-0000-000000000000" // placeholder for save
if err := session.Save(c.Request, c.Writer); err != nil {
log.Error().Err(err).Msg("save oauth state error")
}
isSecure := strings.HasPrefix(h.deps.Config.BaseURL, "https")
http.SetCookie(c.Writer, &http.Cookie{
Name: "oauth_state",
Value: state,
Path: "/",
MaxAge: 600, // 10 minutes
HttpOnly: true,
Secure: isSecure,
SameSite: http.SameSiteLaxMode,
})
url := provider.Config.AuthCodeURL(state, oauth2.AccessTypeOffline)
c.Redirect(http.StatusTemporaryRedirect, url)
@ -73,12 +77,17 @@ func (h *OAuthHandler) Callback(c *gin.Context) {
}
// Verify state
session, _ := h.deps.SessionStore.Get(c.Request, "oauth_state")
expectedState, _ := session.Values["state"].(string)
if c.Query("state") != expectedState {
stateCookie, err := c.Request.Cookie("oauth_state")
if err != nil || c.Query("state") != stateCookie.Value {
c.String(http.StatusBadRequest, "Invalid state parameter")
return
}
// Clear the state cookie
http.SetCookie(c.Writer, &http.Cookie{
Name: "oauth_state",
Path: "/",
MaxAge: -1,
})
code := c.Query("code")
token, err := provider.Config.Exchange(c.Request.Context(), code)
@ -137,12 +146,16 @@ func (h *OAuthHandler) appleLogin(c *gin.Context) {
}
state := generateState()
session, _ := h.deps.SessionStore.Get(c.Request, "oauth_state")
session.Values["state"] = state
session.Values["user_id"] = "00000000-0000-0000-0000-000000000000"
if err := session.Save(c.Request, c.Writer); err != nil {
log.Error().Err(err).Msg("save oauth state error")
}
isSecure := strings.HasPrefix(h.deps.Config.BaseURL, "https")
http.SetCookie(c.Writer, &http.Cookie{
Name: "oauth_state",
Value: state,
Path: "/",
MaxAge: 600, // 10 minutes
HttpOnly: true,
Secure: isSecure,
SameSite: http.SameSiteNoneMode, // Apple uses form_post cross-origin
})
url := appleProvider.Config.AuthCodeURL(state, oauth2.AccessTypeOffline, auth.AppleAuthCodeOption())
c.Redirect(http.StatusTemporaryRedirect, url)
@ -164,12 +177,17 @@ func (h *OAuthHandler) AppleCallback(c *gin.Context) {
code := c.PostForm("code")
state := c.PostForm("state")
session, _ := h.deps.SessionStore.Get(c.Request, "oauth_state")
expectedState, _ := session.Values["state"].(string)
if state != expectedState {
stateCookie, err := c.Request.Cookie("oauth_state")
if err != nil || state != stateCookie.Value {
c.String(http.StatusBadRequest, "Invalid state parameter")
return
}
// Clear the state cookie
http.SetCookie(c.Writer, &http.Cookie{
Name: "oauth_state",
Path: "/",
MaxAge: -1,
})
token, err := appleProvider.ExchangeCode(c.Request.Context(), code)
if err != nil {