package middleware import ( "fmt" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/rs/zerolog/log" ) func ParseToken(tokenString string, jwtSecret string) (string, jwt.MapClaims, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { // Validate the algorithm (Supabase uses HS256 usually) if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(jwtSecret), nil }) if err != nil || !token.Valid { return "", nil, fmt.Errorf("invalid token: %w", err) } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return "", nil, fmt.Errorf("invalid token claims") } // Supabase uses 'sub' field for user ID userID, ok := claims["sub"].(string) if !ok { return "", nil, fmt.Errorf("token missing user ID") } return userID, claims, nil } func AuthMiddleware(jwtSecret string) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"}) c.Abort() return } parts := strings.Split(authHeader, " ") if len(parts) != 2 || parts[0] != "Bearer" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header must be Bearer token"}) c.Abort() return } tokenString := parts[1] userID, claims, err := ParseToken(tokenString, jwtSecret) if err != nil { log.Error().Err(err).Msg("Invalid token") c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) c.Abort() return } // Store user ID and claims in context c.Set("user_id", userID) c.Set("claims", claims) c.Next() } }