package middleware import ( "strings" "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v5" ) // JWTClaims contains extracted claims from the JWT type JWTClaims struct { Subject string `json:"sub"` Email string `json:"email"` Name string `json:"name"` TenantID string `json:"tenantId"` } // JWTExtract extracts JWT claims from the Authorization header for audit context. // This middleware does NOT validate the JWT — it only extracts claims. // Authentication is handled by mTLS + API key. JWT is optional passthrough. func JWTExtract() fiber.Handler { return func(c *fiber.Ctx) error { auth := c.Get("Authorization") if auth == "" || !strings.HasPrefix(auth, "Bearer ") { return c.Next() } tokenStr := strings.TrimPrefix(auth, "Bearer ") // Parse without validation — we trust the API key for auth parser := jwt.NewParser(jwt.WithoutClaimsValidation()) token, _, err := parser.ParseUnverified(tokenStr, jwt.MapClaims{}) if err != nil { // Invalid JWT — ignore, not blocking return c.Next() } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return c.Next() } jwtClaims := &JWTClaims{} if sub, ok := claims["sub"].(string); ok { jwtClaims.Subject = sub } if email, ok := claims["email"].(string); ok { jwtClaims.Email = email } if name, ok := claims["name"].(string); ok { jwtClaims.Name = name } if tid, ok := claims["tenantId"].(string); ok { jwtClaims.TenantID = tid } c.Locals("jwtClaims", jwtClaims) return c.Next() } } // GetJWTClaims retrieves JWT claims from context func GetJWTClaims(c *fiber.Ctx) *JWTClaims { if claims, ok := c.Locals("jwtClaims").(*JWTClaims); ok { return claims } return nil }