fixed clearing of csrf and refresh tokens

This commit is contained in:
2026-02-20 14:41:19 +08:00
parent ed2eb67912
commit 5d94c28378
+82 -28
View File
@@ -603,48 +603,47 @@ func checkEmailInDB(email string) (bool, error) {
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if !isValidAuthHeader(authHeader) {
helper.RespondWithError(w, http.StatusUnauthorized, "Authorization header missing or invalid")
return
}
clearRefreshTokenCookie(w)
clearCSRFCookie(w)
tokenString := strings.TrimSpace(strings.TrimPrefix(authHeader, bearerPrefix))
if tokenString == "" {
helper.RespondWithError(w, http.StatusUnauthorized, "Token is missing or empty")
return
}
if isValidAuthHeader(authHeader) {
tokenString := strings.TrimSpace(strings.TrimPrefix(authHeader, bearerPrefix))
if tokenString != "" {
token, err := jwt.ParseWithClaims(tokenString, &models.AccessToken{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
if rsaPrivateKey == nil {
return nil, errors.New("RSA private key is not initialized")
}
return &rsaPrivateKey.PublicKey, nil
})
token, err := jwt.ParseWithClaims(tokenString, &models.AccessToken{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
if rsaPrivateKey == nil {
return nil, errors.New("RSA private key is not initialized")
}
return &rsaPrivateKey.PublicKey, nil
})
if err == nil {
if claims, ok := token.Claims.(*models.AccessToken); ok {
userID, err := services.GetUserIDFromEmail(claims.Email)
if err == nil {
if err := RevokeAllUserSessions(userID); err != nil {
helper.LogError(err, "Failed to revoke user sessions during logout")
if claims, ok := token.Claims.(*models.AccessToken); ok {
userID, err := services.GetUserIDFromEmail(claims.Email)
if err == nil {
if err := RevokeAllUserSessions(userID); err != nil {
helper.LogError(err, "Failed to revoke user sessions during logout")
}
} else {
helper.LogError(err, "Failed to get user ID during logout")
}
}
} else {
helper.LogError(err, "Failed to get user ID during logout")
helper.LogError(err, "Failed to parse JWT token during logout")
}
} else {
helper.LogWarn("Authorization header contains empty bearer token during logout")
}
} else {
helper.LogError(err, "Failed to parse JWT token during logout")
helper.LogWarn("Authorization header missing or invalid during logout; proceeding with cookie clear only")
}
if err := accessLog(r, nil, 18, nil); err != nil {
helper.LogError(err, "Failed to write access log during logout")
}
clearRefreshTokenCookie(w)
response := map[string]interface{}{
"message": "Successfully logged out",
"action": "clear_session_storage",
@@ -709,3 +708,58 @@ func clearRefreshTokenCookie(w http.ResponseWriter) {
helper.LogInfo("Refresh token cookie clearing commands sent to browser")
}
func clearCSRFCookie(w http.ResponseWriter) {
helper.LogInfo("Clearing csrf_token cookie...")
isSecure := strings.HasPrefix(os.Getenv("BACKEND_URL"), HTTPS)
// Match middleware cookie characteristics first (host-only, SameSiteStrict)
primaryCookie := &http.Cookie{
Name: "csrf_token",
Value: "",
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
Expires: time.Unix(0, 0),
MaxAge: -1,
}
http.SetCookie(w, primaryCookie)
helper.LogInfo(fmt.Sprintf("CSRF cookie clear #1 sent: Name=%s, Domain=%s, Secure=%v, SameSite=%v",
primaryCookie.Name, primaryCookie.Domain, primaryCookie.Secure, primaryCookie.SameSite))
// Fallback for local/dev browser behavior where secure or samesite attributes differ
fallbackCookie := &http.Cookie{
Name: "csrf_token",
Value: "",
Path: "/",
HttpOnly: true,
Secure: isSecure,
SameSite: http.SameSiteLaxMode,
Expires: time.Unix(0, 0),
MaxAge: -1,
}
http.SetCookie(w, fallbackCookie)
helper.LogInfo(fmt.Sprintf("CSRF cookie clear #2 sent: Name=%s, Domain=%s, Secure=%v, SameSite=%v",
fallbackCookie.Name, fallbackCookie.Domain, fallbackCookie.Secure, fallbackCookie.SameSite))
if !isSecure {
localhostCookie := &http.Cookie{
Name: "csrf_token",
Value: "",
Path: "/",
Domain: "localhost",
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteLaxMode,
Expires: time.Unix(0, 0),
MaxAge: -1,
}
http.SetCookie(w, localhostCookie)
helper.LogInfo(fmt.Sprintf("CSRF cookie clear #3 sent: Name=%s, Domain=%s, Secure=%v, SameSite=%v",
localhostCookie.Name, localhostCookie.Domain, localhostCookie.Secure, localhostCookie.SameSite))
}
helper.LogInfo("CSRF token cookie clearing commands sent to browser")
}