---
title: "GN011 – Apply Cross-Cutting Concerns via Middleware"
impact: medium
impactDescription: "Duplicating auth checks, rate limiting, or logging inside individual handlers makes enforcement inconsistent and maintenance expensive."
tags: [go, gin, middleware, architecture]
---

# GN011 – Apply Cross-Cutting Concerns via Middleware

## Rule

Authentication, authorization, rate limiting, request logging, request ID injection, and CORS must be implemented as Gin middleware functions and applied at the router/group level — not duplicated inside individual handlers.

## Why

When authentication logic lives inside each handler, a single missed handler creates a security gap. Middleware ensures uniform enforcement. It also separates concerns: handlers contain only business logic; infrastructure concerns live in middleware.

## Wrong

```go
// ❌ Auth duplicated in every handler
func (h *OrderHandler) List(c *gin.Context) {
    token := c.GetHeader("Authorization")
    userID, err := h.auth.ValidateToken(token)
    if err != nil {
        c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
        return
    }
    // ... business logic
}

func (h *OrderHandler) Create(c *gin.Context) {
    token := c.GetHeader("Authorization")   // ❌ duplicated
    userID, err := h.auth.ValidateToken(token)
    // ...
}
```

## Correct

```go
// middleware/auth.go
func JWTAuth(tokenValidator TokenValidator) gin.HandlerFunc {
    return func(c *gin.Context) {
        token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
        claims, err := tokenValidator.Validate(token)
        if err != nil {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            return
        }
        c.Set("user_id", claims.UserID)
        c.Set("user_role", claims.Role)
        c.Next()
    }
}

// middleware/request_id.go
func RequestID() gin.HandlerFunc {
    return func(c *gin.Context) {
        id := c.GetHeader("X-Request-ID")
        if id == "" {
            id = uuid.New().String()
        }
        c.Set("request_id", id)
        c.Header("X-Request-ID", id)
        c.Next()
    }
}

// main.go — applied at group level (see GN005)
r := gin.New()
r.Use(gin.Recovery(), RequestID(), StructuredLogger(logger))

api := r.Group("/api/v1")
api.Use(JWTAuth(tokenValidator))
api.Use(RateLimit(100, time.Minute))

api.GET("/orders", orderHandler.List)   // auth + rate limit automatically applied
api.POST("/orders", orderHandler.Create)

// handlers are clean — no auth logic
func (h *OrderHandler) List(c *gin.Context) {
    userID := c.GetString("user_id")   // set by middleware
    // ... just business logic
}
```

## Notes

- Middleware should only set values in the context and call `c.Next()` or `c.Abort()` — no response writing for non-auth middleware.
- Return factory functions (`func JWTAuth(dep Dep) gin.HandlerFunc`) so middleware is injectable and testable.
- Always document what a middleware sets in the context (e.g. `"user_id"`, `"request_id"`) so handlers know what keys are available.
