Skip to content

Repository files navigation

gin-middleware

简体中文 | English

A collection of production-ready middleware for the Gin web framework.

Features

  • JWT Authentication — Token generation, parsing, and Gin middleware with configurable secrets, expiration, and signing methods
  • CORS — Configurable cross-origin resource sharing middleware
  • Request — JSON binding with defaults applied before Gin validation
  • Response — Standardized API response formatting helpers
  • Log — Leveled logging interface with pluggable backends

Installation

go get github.com/jackman0925/gin-middleware

Upgrading from v0.0.2 and Earlier

This release keeps the existing exported functions and fields, so most applications should continue to compile. It does, however, intentionally change several runtime behaviors for safer production defaults. Review the following items before upgrading:

JWT expiration

JWT validation now requires an exp claim by default. Tokens generated by this library already contain exp and continue to work. If another service or an older integration issues non-expiring tokens, enable temporary compatibility:

j := jwt.NewWithConfig(jwt.Config{
    Secret:                 "your-secret-key-must-have-32-bytes",
    AllowMissingExpiration: true,
})

AllowMissingExpiration should be used only during migration. Prefer updating the token issuer to include exp, then remove this option.

JWT claims in Gin Context

JWT claims are no longer copied into arbitrary Gin Context keys because they could overwrite values written by other middleware. Replace direct access such as:

adminID, exists := c.Get("adminID")

with namespaced claim access:

claims, exists := jwt.ClaimsFromContext(c)
if exists {
    adminID := claims["adminID"]
}

Use jwt.UsernameFromContext(c) for the username claim.

Error responses

errorhandler.ErrorHandler() now returns internal server error for private errors instead of exposing err.Error() to clients. Mark only client-safe errors as public:

c.Error(errors.New("safe client message")).SetType(gin.ErrorTypePublic)

Applications that require custom status and message mapping should use errorhandler.ErrorHandlerWithMapper.

CORS wildcard credentials

cors.New([]string{"*"}) no longer enables credentials, because browsers reject the combination of Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true. To allow credentialed requests, list trusted origins explicitly. A custom wildcard configuration with credentials reflects the request Origin and sends Vary: Origin, but explicit origins are recommended for production.

Logging

Logging remains disabled when no logger is configured. After calling SetLogger or SetStdLogger, logging is enabled by default. Use log.SetEnabled(false) when it needs to be disabled at runtime.

Usage

Logging

All middleware packages share a common logging interface.

By default, logging is disabled (discard). If you don't need logs, no logging setup is required:

// 什么都不需要做 — 日志默认关闭
import (
    "github.com/jackman0925/gin-middleware/jwt"
    "github.com/jackman0925/gin-middleware/cors"
)

If you want to see logs:

import "github.com/jackman0925/gin-middleware/log"

// 标准库日志
log.SetStdLogger(log.LevelInfo)
// 配置 logger 后默认开启;可以随时关闭并重新开启
log.SetEnabled(false)
log.SetEnabled(true)

// 接入 glog(github.com/jackman0925/glog)— 只需 4 行
type glogAdapter struct{}
func (glogAdapter) Errorf(f string, v ...any) { glog.Errorf(f, v...) }
func (glogAdapter) Warnf(f string, v ...any)  { glog.Warnf(f, v...) }
func (glogAdapter) Infof(f string, v ...any)  { glog.Infof(f, v...) }
func (glogAdapter) Debugf(f string, v ...any) { glog.Debugf(f, v...) }
log.SetLogger(glogAdapter{}, log.LevelDebug)

Calling log.SetLogger(nil, level) removes the configured logger and disables logging. log.IsEnabled() reports whether a logger is both configured and enabled. Runtime logger configuration and toggling are concurrency-safe.

JSON Request Binding and Defaults

Use the request package when JSON fields need defaults before validation:

import "github.com/jackman0925/gin-middleware/request"

type CreateUserRequest struct {
    Name    string `json:"name" binding:"required"`
    Page    int    `json:"page" default:"1" binding:"required,min=1"`
    Enabled bool   `json:"enabled" default:"true"`
}

func createUser(c *gin.Context) {
    var req CreateUserRequest
    if err := request.BindJSON(c, &req); err != nil {
        response.Fail(c, http.StatusBadRequest, err)
        return
    }
    // Missing page and enabled become 1 and true before validation.
}

BindJSON decodes the body, applies default:"..." tags, then invokes Gin's validator. It therefore supports default together with binding:"required". Explicit JSON values such as false, 0, and "" are preserved rather than overwritten. Supported scalar defaults are string, bool, integer, unsigned integer, float, time.Duration (for example default:"5s"), and RFC3339 time.Time; slices, maps, arrays, and structs use a JSON literal in the tag.

The request body is cached in Gin's BodyBytesKey, so it remains available to ShouldBindBodyWithJSON. SetReqDefaults is available for non-JSON values; it treats zero values as unset, so prefer BindJSON for JSON requests.

JWT Authentication

import "github.com/jackman0925/gin-middleware/jwt"

// Create JWT middleware with default config (HS256, 72h expiration)
j := jwt.New("your-32-char+ secret key here!!")

// Generate a token
token, err := j.GenerateTokenWithUsername("admin", map[string]interface{}{
    "adminID": 1,
    "role":    "admin",
})

// Use as Gin middleware
r := gin.Default()
admin := r.Group("/admin")
admin.Use(j.Middleware())
{
    admin.GET("/dashboard", func(c *gin.Context) {
        username, _ := jwt.UsernameFromContext(c)
        c.JSON(200, gin.H{"username": username})
    })
}

Custom configuration:

import jwtlib "github.com/golang-jwt/jwt/v5"

j := jwt.NewWithConfig(jwt.Config{
    Secret:          "your-secret-key-must-have-32-bytes",
    TokenHeaderName: "Authorization",
    TokenPrefix:     "Bearer",
    Expiration:      time.Hour * 24,
    SigningMethod:   jwtlib.SigningMethodHS256,
    Issuer:          "my-service", // optional
    Audience:        "my-client",  // optional
    Leeway:          30 * time.Second,
})

JWT validation accepts only the configured HMAC algorithm and requires an exp claim by default. Set AllowMissingExpiration only when compatibility with non-expiring legacy tokens is required. Claims are stored under a package-owned Gin context key; use jwt.ClaimsFromContext and jwt.UsernameFromContext instead of reading arbitrary context keys.

CORS

import "github.com/jackman0925/gin-middleware/cors"

r := gin.Default()

// Allow specific origins
r.Use(cors.New([]string{"https://example.com", "https://app.example.com"}))

// Or allow all (development)
r.Use(cors.AllowAll())

Custom configuration:

r.Use(cors.NewWithConfig(cors.Config{
    AllowedOrigins:   []string{"https://example.com"},
    AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE"},
    AllowedHeaders:   []string{"Content-Type", "Authorization"},
    AllowCredentials: true,
    MaxAge:           86400,
}))

cors.New([]string{"*"}) disables credentials because browsers do not accept wildcard origins with credentialed requests. If a custom configuration combines AllowedOrigins: []string{"*"} with AllowCredentials: true, the middleware reflects the concrete request origin and adds Vary: Origin.

Error Handling

import "github.com/jackman0925/gin-middleware/errorhandler"

r.Use(errorhandler.ErrorHandler())

Private errors receive a generic internal server error response while their details remain available to the configured logger. Mark an error with gin.ErrorTypePublic only when its text is safe for clients, or use ErrorHandlerWithMapper to map application errors to statuses and messages.

Response Helpers

import "github.com/jackman0925/gin-middleware/response"

r.GET("/api/users", func(c *gin.Context) {
    users := getUsers()
    response.Success(c, users)

    // Optional application code, independent from HTTP 200
    response.SuccessWithCode(c, 1001, "users loaded", users)
})

r.GET("/api/products", func(c *gin.Context) {
    products := getProducts()
    response.SuccessPagination(c, products, page, pageSize, total)
})

r.GET("/api/item/:id", func(c *gin.Context) {
    item, err := getItem(id)
    if err != nil {
        response.Fail(c, http.StatusNotFound, err)
        return
    }
    response.Success(c, item)
})

Use FailWithCode and SuccessPaginationWithCode when the API contract uses application-specific codes rather than HTTP statuses as response codes.

License

MIT

About

gin-middleware

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages