Broken Authentication

Broken Authentication in Go (Gin) Guide [GHSA-3mv2-vmwh-rwfx]

[Updated May 2026] Updated GHSA-3mv2-vmwh-rwfx

Overview

Broken Authentication in web applications enables attackers to impersonate legitimate users by stealing or abusing credentials or session tokens. In Go apps using the Gin framework, this often translates into cookies or tokens that are easy to predict, reused, or not bound to a server-side session, allowing access to sensitive endpoints, data, and admin functionality. Common Gin-specific manifestations include storing a user identifier directly in a client cookie, failing to mark cookies as HttpOnly or Secure, and skipping session revocation or rotation. Without server-side validation or cryptographic binding of tokens to a user, an attacker can reuse a stolen cookie or JWT to gain unauthorized access, perform actions, or read protected data. To detect and remediate, audit authentication flows: login, logout, session creation, and token validation. Enforce TLS, set cookie attributes properly, store sessions server-side or use signed, expiring tokens, and implement token rotation on login/logout and expiration checks. Consider using gin-contrib/sessions with a server-side store or a robust JWT approach with strict claims validation. Operationally, combine with rate limiting, MFA, and account lockouts to slow credential stuffing, and align with secure default configurations across environments. The sample remediation below demonstrates a vulnerable pattern and a secure alternative in a compact Go Gin app.

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
  "crypto/rand"
  "encoding/hex"
  "net/http"

  "github.com/gin-gonic/gin"
)

var vulnSessions = map[string]string{}
var fixSessions = map[string]string{}

func main() {
  r := gin.Default()
  // Vulnerable pattern: identity stored in a client cookie without server-side binding
  r.POST("/login/vuln", vulnLogin)
  r.GET("/protected/vuln", vulnAuthMiddleware, vulnProtected)

  // Secure pattern: server-side session store with HttpOnly/Secure cookie and token rotation
  r.POST("/login/fix", fixLogin)
  r.GET("/protected/fix", fixAuthMiddleware, fixProtected)

  r.Run(":8080")
}

// Vulnerable login: sets a cookie with the user identity directly (insecure)
func vulnLogin(c *gin.Context) {
  user := c.PostForm("username")
  pass := c.PostForm("password")
  if user == "admin" && pass == "password" {
    c.SetCookie("v_session", user, 3600, "/", "", false, false)
    vulnSessions[user] = user
    c.JSON(http.StatusOK, gin.H{"status": "logged in (vulnerable)"})
    return
  }
  c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid"})
}

func vulnAuthMiddleware(c *gin.Context) {
  token, err := c.Cookie("v_session")
  if err != nil {
    c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
    return
  }
  if user, ok := vulnSessions[token]; ok {
    c.Set("user", user)
    c.Next()
    return
  }
  c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
}

func vulnProtected(c *gin.Context) {
  user, _ := c.Get("user")
  c.JSON(http.StatusOK, gin.H{"hello": user})
}

// Secure login: uses server-side session store and a cryptographically random token
func fixLogin(c *gin.Context) {
  user := c.PostForm("username")
  pass := c.PostForm("password")
  if user == "admin" && pass == "password" {
    b := make([]byte, 32)
    if _, err := rand.Read(b); err != nil {
      c.JSON(http.StatusInternalServerError, gin.H{"error": "server_error"})
      return
    }
    token := hex.EncodeToString(b)
    fixSessions[token] = user
    // HttpOnly and (domain/secure) depending on deployment; shown here with HttpOnly
    c.SetCookie("session_token", token, 3600, "/", "", false, true)
    c.JSON(http.StatusOK, gin.H{"status": "logged in (fixed)"})
    return
  }
  c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid"})
}

func fixAuthMiddleware(c *gin.Context) {
  token, err := c.Cookie("session_token")
  if err != nil {
    c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
    return
  }
  if user, ok := fixSessions[token]; ok {
    c.Set("user", user)
    c.Next()
    return
  }
  c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
}

func fixProtected(c *gin.Context) {
  user, _ := c.Get("user")
  c.JSON(http.StatusOK, gin.H{"hello": user})
}

CVE References

Choose which optional cookies to allow. You can change this any time.