gin-jwt

authentication-libraryGoGinJWTmiddlewaresecurityweb-framework

Authentication Library

gin-jwt

Overview

gin-jwt is a JWT authentication middleware specifically designed for the Go Gin framework. It provides a simple API for JWT token generation, validation, and refresh, enabling easy integration of authentication features into Gin applications. It offers standard flows for login, logout, and authentication checks, fully compatible with Gin's middleware patterns. As of 2025, it continues to be actively developed and is one of the most popular JWT authentication solutions for Go web applications.

Details

gin-jwt is a specialized middleware for integrating JWT (JSON Web Token) authentication with the Gin framework. It offers the following key features:

  • Gin Native Integration: Full compatibility with Gin framework middleware patterns
  • Simple API: Minimal configuration required to implement JWT authentication
  • Token Management: Automatic management of access and refresh tokens
  • Customizable: Flexible customization of token generation and validation logic
  • Security Features: Support for token expiration, signature verification, and claims validation
  • RESTful API Support: Unified error handling with JSON responses and HTTP status codes

Pros and Cons

Pros

  • Easy implementation through perfect integration with Gin framework
  • Minimal boilerplate code required to add authentication features
  • Secure token management following JWT standards
  • Customizable authentication logic and claims processing
  • JSON-formatted responses suitable for RESTful APIs
  • Active community support and comprehensive documentation

Cons

  • Gin framework exclusive, cannot be used with other Go frameworks
  • Does not support complex authentication requirements (OAuth, SAML, etc.)
  • Integration with external authentication providers requires separate implementation
  • Does not include token persistence functionality

Reference Pages

Code Examples

Basic Setup

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/appleboy/gin-jwt/v2"
	"github.com/gin-gonic/gin"
)

type login struct {
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

type User struct {
	UserName  string `json:"username"`
	FirstName string `json:"firstname"`
	LastName  string `json:"lastname"`
}

func main() {
	r := gin.Default()

	// JWT middleware configuration
	authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
		Realm:       "test zone",
		Key:         []byte("secret key"),
		Timeout:     time.Hour,
		MaxRefresh:  time.Hour,
		IdentityKey: "id",
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if v, ok := data.(*User); ok {
				return jwt.MapClaims{
					"id":   v.UserName,
					"name": v.FirstName + " " + v.LastName,
				}
			}
			return jwt.MapClaims{}
		},
		IdentityHandler: func(c *gin.Context) interface{} {
			claims := jwt.ExtractClaims(c)
			return &User{
				UserName: claims["id"].(string),
			}
		},
		Authenticator: func(c *gin.Context) (interface{}, error) {
			var loginVals login
			if err := c.ShouldBind(&loginVals); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			userID := loginVals.Username
			password := loginVals.Password

			// Actual authentication logic
			if userID == "admin" && password == "admin" {
				return &User{
					UserName:  userID,
					FirstName: "Admin",
					LastName:  "User",
				}, nil
			}

			return nil, jwt.ErrFailedAuthentication
		},
		Authorizator: func(data interface{}, c *gin.Context) bool {
			if v, ok := data.(*User); ok && v.UserName == "admin" {
				return true
			}
			return false
		},
		Unauthorized: func(c *gin.Context, code int, message string) {
			c.JSON(code, gin.H{
				"code":    code,
				"message": message,
			})
		},
		TokenLookup: "header: Authorization, query: token, cookie: jwt",
		TokenHeadName: "Bearer",
		TimeFunc: time.Now,
	})

	if err != nil {
		log.Fatal("JWT Error:" + err.Error())
	}

	// Route setup
	r.POST("/login", authMiddleware.LoginHandler)
	r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
		claims := jwt.ExtractClaims(c)
		log.Printf("NoRoute claims: %#v\n", claims)
		c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
	})

	auth := r.Group("/auth")
	auth.GET("/refresh_token", authMiddleware.RefreshHandler)
	auth.Use(authMiddleware.MiddlewareFunc())
	{
		auth.GET("/hello", helloHandler)
		auth.GET("/profile", profileHandler)
	}

	r.Run(":8080")
}

Protected Endpoint Implementation

func helloHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	user, _ := c.Get("id")
	c.JSON(200, gin.H{
		"userID": claims["id"],
		"name":   claims["name"],
		"text":   "Hello World.",
		"user":   user.(*User).UserName,
	})
}

func profileHandler(c *gin.Context) {
	user, _ := c.Get("id")
	claims := jwt.ExtractClaims(c)
	
	c.JSON(200, gin.H{
		"user_id":    user.(*User).UserName,
		"first_name": claims["name"],
		"claims":     claims,
		"profile": gin.H{
			"email":       "[email protected]",
			"role":        "administrator",
			"permissions": []string{"read", "write", "delete"},
		},
	})
}

Role-Based Access Control

type Role string

const (
	AdminRole Role = "admin"
	UserRole  Role = "user"
	GuestRole Role = "guest"
)

func createJWTMiddleware() *jwt.GinJWTMiddleware {
	return &jwt.GinJWTMiddleware{
		Realm:      "api",
		Key:        []byte("your-secret-key"),
		Timeout:    time.Hour * 24,
		MaxRefresh: time.Hour * 24 * 7,
		
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if user, ok := data.(*User); ok {
				return jwt.MapClaims{
					"id":   user.UserName,
					"role": user.Role,
					"exp":  time.Now().Add(time.Hour * 24).Unix(),
				}
			}
			return jwt.MapClaims{}
		},
		
		Authorizator: func(data interface{}, c *gin.Context) bool {
			requiredRole := c.GetString("required_role")
			if requiredRole == "" {
				return true // No role restriction
			}
			
			claims := jwt.ExtractClaims(c)
			userRole := claims["role"].(string)
			
			// Role hierarchy check
			switch requiredRole {
			case "admin":
				return userRole == "admin"
			case "user":
				return userRole == "admin" || userRole == "user"
			default:
				return true
			}
		},
	}
}

// Role restriction middleware
func requireRole(role string) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("required_role", role)
		c.Next()
	}
}

// Usage example
func setupRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) {
	api := r.Group("/api")
	api.Use(authMiddleware.MiddlewareFunc())
	{
		// Accessible to all users
		api.GET("/profile", profileHandler)
		
		// Requires user role or higher
		api.GET("/data", requireRole("user"), dataHandler)
		
		// Admin only
		api.POST("/admin/users", requireRole("admin"), createUserHandler)
		api.DELETE("/admin/users/:id", requireRole("admin"), deleteUserHandler)
	}
}

Integration with Custom Authentication Provider

type AuthService struct {
	db *sql.DB
}

func (a *AuthService) AuthenticateUser(username, password string) (*User, error) {
	// Database authentication
	row := a.db.QueryRow("SELECT id, username, password_hash, role FROM users WHERE username = ?", username)
	
	var user User
	var passwordHash string
	err := row.Scan(&user.ID, &user.UserName, &passwordHash, &user.Role)
	if err != nil {
		return nil, errors.New("user not found")
	}
	
	// Password verification
	if !checkPasswordHash(password, passwordHash) {
		return nil, errors.New("invalid password")
	}
	
	return &user, nil
}

func setupJWTWithDB(authService *AuthService) *jwt.GinJWTMiddleware {
	return &jwt.GinJWTMiddleware{
		Authenticator: func(c *gin.Context) (interface{}, error) {
			var loginVals login
			if err := c.ShouldBind(&loginVals); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			
			user, err := authService.AuthenticateUser(loginVals.Username, loginVals.Password)
			if err != nil {
				return nil, jwt.ErrFailedAuthentication
			}
			
			// Log login activity
			go authService.LogLogin(user.ID, c.ClientIP())
			
			return user, nil
		},
		
		LogoutResponse: func(c *gin.Context, code int) {
			// Logout processing
			claims := jwt.ExtractClaims(c)
			if userID, exists := claims["id"]; exists {
				go authService.LogLogout(userID.(string), c.ClientIP())
			}
			
			c.JSON(http.StatusOK, gin.H{
				"code":    http.StatusOK,
				"message": "Successfully logged out",
			})
		},
	}
}

Error Handling and Security

func setupSecureJWT() *jwt.GinJWTMiddleware {
	return &jwt.GinJWTMiddleware{
		// Secure settings
		SecureCookie:    true, // HTTPS required
		CookieHTTPOnly:  true, // XSS protection
		CookieSameSite:  http.SameSiteStrictMode,
		CookieName:      "jwt-token",
		
		// Error handling
		HTTPStatusMessageFunc: func(e error, c *gin.Context) string {
			switch e {
			case jwt.ErrFailedAuthentication:
				return "Invalid username or password"
			case jwt.ErrFailedTokenCreation:
				return "Failed to create token"
			case jwt.ErrExpiredToken:
				return "Token has expired"
			case jwt.ErrEmptyAuthHeader:
				return "Authorization header is required"
			default:
				return "Authentication failed"
			}
		},
		
		// Custom claims validation
		ClaimsValidator: func(claims jwt.MapClaims) error {
			// Issuer check
			if claims["iss"] != "your-app" {
				return errors.New("invalid issuer")
			}
			
			// Audience check
			if claims["aud"] != "your-api" {
				return errors.New("invalid audience")
			}
			
			return nil
		},
		
		// Rate limiting
		LoginResponse: func(c *gin.Context, code int, token string, expire time.Time) {
			// Rate limiting check
			clientIP := c.ClientIP()
			if isRateLimited(clientIP) {
				c.JSON(http.StatusTooManyRequests, gin.H{
					"code":    http.StatusTooManyRequests,
					"message": "Too many login attempts",
				})
				return
			}
			
			c.JSON(http.StatusOK, gin.H{
				"code":   http.StatusOK,
				"token":  token,
				"expire": expire.Format(time.RFC3339),
			})
		},
	}
}