Gin JWT Claims Middleware

Extract and verify JWT claims in Gin middleware, checking issuer and role before handlers, with claims injected into context.

GoAuth Jwt

Code

snippet
package main

import (
	"errors"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
)

var errInvalidToken = errors.New("invalid token")

type AppClaims struct {
	UserID string `json:"uid"`
	Role   string `json:"role"`
	jwt.RegisteredClaims
}

func authMiddleware(secret string, allowedRoles ...string) gin.HandlerFunc {
	return func(c *gin.Context) {
		header := c.GetHeader("Authorization")
		if !strings.HasPrefix(header, "Bearer ") {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing bearer token"})
			return
		}

		token, err := jwt.ParseWithClaims(
			strings.TrimPrefix(header, "Bearer "),
			&AppClaims{},
			func(t *jwt.Token) (interface{}, error) {
				if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
					return nil, errors.New("unexpected signing method")
				}
				return []byte(secret), nil
			},
			jwt.WithIssuer("auth.example.com"),
			jwt.WithExpirationRequired(),
		)
		if err != nil || !token.Valid {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": errInvalidToken.Error()})
			return
		}

		claims := token.Claims.(*AppClaims)
		for _, role := range allowedRoles {
			if claims.Role == role {
				c.Set("claims", claims)
				c.Next()
				return
			}
		}
		c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "role not permitted"})
	}
}

func main() {
	r := gin.New()
	r.GET("/admin/stats", authMiddleware("secret", "admin"), func(c *gin.Context) {
		claims := c.MustGet("claims").(*AppClaims)
		c.JSON(http.StatusOK, gin.H{"for": claims.UserID, "generated": time.Now().Unix()})
	})
	r.Run(":8080")
}