Jwt Misconfiguration in Gin
How Jwt Misconfiguration Manifests in Gin
Jwt misconfiguration in Gin applications often stems from improper middleware setup or inadequate validation of JWT claims. The most common vulnerability occurs when developers use Gin's ginjwt middleware without properly configuring the signing method verification.
A critical misconfiguration happens when JWTs accept any signing method, including none. Consider this vulnerable Gin setup:
package main
import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
func main() {
r := gin.Default()
r.GET("/public", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "public"})
})
r.GET("/protected", func(c *gin.Context) {
token := jwt.NewWithClaims(jwt.SigningMethodNone, jwt.MapClaims{
"user_id": 1,
})
tokenString, _ := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
c.JSON(200, gin.H{"token": tokenString})
})
r.Run(":8080")
}
This code demonstrates a JWT with no signature, which any client can forge. When the server accepts SigningMethodNone without verification, attackers can create arbitrary tokens.
Another common Gin-specific issue involves improper claim validation. Developers often extract claims without checking their validity:
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
claims := jwt.MapClaims{}
// Vulnerable: accepts any signing method
token, err := jwt.ParseWithClaims(tokenString, claims, nil)
if err != nil {
c.JSON(401, gin.H{"error": "unauthorized"})
c.Abort()
return
}
// Missing claim validation
c.Set("user", claims["user_id"])
c.Next()
}
}
This middleware fails to verify the signing method and doesn't validate required claims like exp (expiration) or nbf (not before).
Gin's context-based architecture can also lead to subtle misconfigurations. Developers might store sensitive data in the context without proper type assertions:
func GetUserID(c *gin.Context) int {
// Vulnerable: no type checking, could be nil or wrong type
return c.Get("user_id").(int)
}
If an attacker can manipulate the context or bypass authentication, this could return arbitrary values or cause panics.
Gin-Specific Detection
Detecting JWT misconfigurations in Gin applications requires both static analysis and runtime scanning. middleBrick's black-box scanning approach is particularly effective for identifying these issues without needing source code access.
middleBrick scans for JWT vulnerabilities by sending crafted requests to your API endpoints. For Gin applications, it specifically tests:
- None algorithm acceptance by sending unsigned tokens
- Weak key usage by attempting brute-force attacks
- Missing claim validation by crafting tokens with manipulated claims
- Algorithm confusion attacks using RS256 public keys with HS256 verification
The scanning process takes 5-15 seconds and provides a security score with specific findings. For example, middleBrick might detect:
{
"findings": [
{
"category": "Authentication",
"severity": "high",
"title": "JWT accepts None signing algorithm",
"description": "The API accepts JWT tokens signed with the 'none' algorithm, allowing attackers to forge authentication tokens.",
"remediation": "Configure JWT middleware to reject none algorithm and validate signing methods."
}
]
}
For development teams using Gin, middleBrick's CLI tool provides quick local scanning:
npx middlebrick scan http://localhost:8080/api
This command scans all API endpoints and returns a detailed report. The GitHub Action integration allows teams to scan staging APIs before deployment:
name: Security Scan
on: [pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run middleBrick Scan
run: |
npx middlebrick scan ${{ secrets.API_URL }}
continue-on-error: true
middleBrick also analyzes OpenAPI specifications alongside runtime scanning. If your Gin application exposes a Swagger spec, middleBrick cross-references the documented authentication requirements with actual behavior, identifying discrepancies between specification and implementation.
Gin-Specific Remediation
Properly configuring JWT middleware in Gin requires several key practices. Here's a secure implementation:
package main
import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"time"
)
type Claims struct {
UserID int `json:"user_id"`
Email string `json:"email"`
jwt.RegisteredClaims
}
func GenerateToken(userID int, email string) (string, error) {
claims := Claims{
UserID: userID,
Email: email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Use a strong secret key stored in environment variables
return token.SignedString([]byte(os.Getenv("JWT_SECRET")))
}
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.JSON(401, gin.H{"error": "missing token"})
c.Abort()
return
}
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
// Verify signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrInvalidKey
}
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil {
c.JSON(401, gin.H{"error": "invalid token"})
c.Abort()
return
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
// Validate required claims
if claims.UserID == 0 || claims.ExpiresAt.Before(time.Now()) {
c.JSON(401, gin.H{"error": "invalid claims"})
c.Abort()
return
}
c.Set("user_id", claims.UserID)
c.Set("email", claims.Email)
c.Next()
} else {
c.JSON(401, gin.H{"error": "invalid token"})
c.Abort()
}
}
}
func main() {
r := gin.Default()
r.GET("/public", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "public"})
})
// Apply middleware to protected routes
api := r.Group("/api")
api.Use(AuthMiddleware())
api.GET("/user", func(c *gin.Context) {
userID := c.GetInt("user_id")
c.JSON(200, gin.H{"user_id": userID})
})
r.Run(":8080")
}
This implementation includes several critical security measures:
- Explicitly verifies the signing method is HMAC
- Validates required claims (UserID, expiration)
- Uses strong secret keys from environment variables
- Properly handles token parsing errors
- Sets user data in context with type safety
For teams using Gin with external identity providers, consider using the github.com/appleboy/gin-jwt library with proper configuration:
authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
Realm: "test zone",
Key: []byte(os.Getenv("JWT_SECRET")),
Timeout: time.Hour,
MaxRefresh: time.Hour,
IdentityKey: "id",
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*model.User); ok {
return jwt.MapClaims{
"user_id": v.ID,
"email": v.Email,
}
}
return jwt.MapClaims{}
},
Authenticator: func(c *gin.Context) (interface{}, error) {
var login model.Login
if err := c.ShouldBind(&login); err != nil {
return nil, jwt.ErrMissingLoginValues
}
// Verify credentials against database
user, err := model.Authenticate(login.Username, login.Password)
if err != nil {
return nil, jwt.ErrFailedAuthentication
}
return user, nil
},
})
Remember that JWT security extends beyond middleware configuration. Always use HTTPS to prevent token interception, implement proper token rotation strategies, and consider adding IP-based restrictions or device fingerprinting for sensitive operations.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |