gin-jwt
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),
})
},
}
}