Overview
Broken authentication can be devastating in web applications, enabling attackers to impersonate users, escalate privileges, or access sensitive data without valid credentials. In Go applications using the Gin framework, common issues include insecure session handling, cookies lacking HttpOnly/Secure flags, and weak or mismanaged tokens (such as unsigned or poorly signed JWTs). When session state is stored in client-accessible cookies or tokens are not verified on every request, an attacker can replay or forge tokens to gain access as legitimate users. These weaknesses often affect production services exposing login endpoints, session endpoints, or sensitive APIs behind authentication checks. Without proper controls, even seemingly straightforward login flows can become vectors for account compromise and data leakage.
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"github.com/golang-jwt/jwt/v4"
)
var usersPlain = map[string]string{
"alice": "password123",
}
var hashedUsers = map[string]string{}
var jwtSecret = []byte("supersecretkey")
func main() {
// Pre-hash the password for the fixed path
if h, err := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost); err == nil {
hashedUsers["alice"] = string(h)
}
r := gin.Default()
r.POST("/vulnerable/login", vulnerableLogin)
r.GET("/vulnerable/secure", vulnerableSecure)
r.POST("/fixed/login", fixedLogin)
r.GET("/fixed/secure", fixedSecure)
r.Run(":8080")
}
// Vulnerable: plaintext password check and insecure cookie
func vulnerableLogin(c *gin.Context) {
var creds struct{ User string `json:"user"`; Pass string `json:"pass"` }
if err := c.BindJSON(&creds); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
return
}
if p, ok := usersPlain[creds.User]; ok && p == creds.Pass {
// Insecure: plaintext session cookie, not HttpOnly/Secure, not bound to server state
c.SetCookie("session", creds.User, 3600, "/", "localhost", false, false)
c.JSON(http.StatusOK, gin.H{"status": "logged_in"})
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
}
// Fixed: use bcrypt and signed JWT in secure cookie
func fixedLogin(c *gin.Context) {
var creds struct{ User string `json:"user"`; Pass string `json:"pass"` }
if err := c.BindJSON(&creds); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
return
}
hash, ok := hashedUsers[creds.User]
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(creds.Pass)); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": creds.User,
"exp": time.Now().Add(time.Hour).Unix(),
})
tokenString, _ := token.SignedString(jwtSecret)
c.SetCookie("auth_token", tokenString, 3600, "/", "localhost", true, true)
c.JSON(http.StatusOK, gin.H{"status": "logged_in"})
}
// Vulnerable: check for any non-empty session cookie
func vulnerableSecure(c *gin.Context) {
if s, err := c.Cookie("session"); err == nil && s != "" {
c.JSON(http.StatusOK, gin.H{"secure": "ok"})
return
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
}
// Fixed: validate JWT from cookie
func fixedSecure(c *gin.Context) {
t, err := c.Cookie("auth_token")
if err != nil || t == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
token, err := jwt.Parse(t, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrInvalidKey
}
return jwtSecret, nil
})
if err != nil || !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"secure": "ok"})
}