Go: Go Middleware and JWT Authentication

Last updated: 2026-08-26

Middleware is the "pipeline" of Go microservices—authentication, rate limiting, logging, and recovery; each cross-cutting concern is a piece of وسيط.

When you need to verify JWT authentication, log activity, or enforce rate limits at every API endpoint, the وسيط pattern allows you to write the code once and have it apply everywhere.

1. You will learn



2. A True Story of a Backend Engineer

(1) Pain point: Manually checking the token at each router; I forgot to do it three times.

Charlie is on the API Gateway team and is responsible for authentication for all endpoints:

"The boss required that all APIs validate JWT tokens. I copied and pasted 15 lines of token validation code into each handler. On the second day after deployment, I forgot to add authentication to /admin/users—anyone could delete users directly. The CTO said, 'The security audit found a serious vulnerability.'"

GO
// Bad code: manually check in every handler
func deleteUser(w http.ResponseWriter, r *http.Request) {
    // Forgot to add Token verification! Serious security vulnerability
    userID := r.PathValue("id")
    deleteUserFromDB(userID)
}

func updateOrder(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("Authorization")
    // Copy 15 lines of verification code each time
    if !validateToken(token) {
        http.Error(w, "unauthorized", 401)
        return
    }
    // Business logic...
}

(2) Go Solution: Authentication Middleware

GO
// Good code: auth وسيط, implement once, apply everywhere
func AuthMiddleware(jwtSecret سلسلة) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            token := extractToken(r)
            claims, err := validateJWT(token, jwtSecret)
            if err != nil {
                writeError(w, http.StatusUnauthorized, "invalid token")
                return
            }
            // Inject user info into Context
            ctx := context.WithValue(r.Context(), "user", claims)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// Registration only needs one Middleware
mux.Handle("POST /api/orders", AuthMiddleware(secret)(orderHandler))

(3) Benefits: Manual vs. Middleware

Dimension Manual Check Middleware Pattern
Risk of Omission There is a risk of forgetting to add each new route Zero
Code duplication 15 lines per handler 0 lines (handlers focus solely on business logic)
Modify logic Modify 20 handlers Modify 1 middleware
Unit Testing Test authentication for each handler Test the middleware only once


3. JWT Authentication

▶ Example: JWT Generation and Validation

⚙️ Prerequisite: In production, use go get github.com/golang-jwt/jwt/v5. The code below is a manual implementation for educational purposes only.

GO 📖 Display only
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "strings"
    "time"
)

// ---------- JWT manual impl (no third-party dependency) ----------

type JWTClaims struct {
    UserID      int      `json:"user_id"`
    Role        string   `json:"role"`
    Permissions []string `json:"permissions"`
    ExpiresAt   int64    `json:"exp"`
}

type JWTHeader struct {
    Alg string `json:"alg"`
    Typ string `json:"typ"`
}

func base64Encode(data []byte) string {
    return strings.TrimRight(base64.URLEncoding.EncodeToString(data), "=")
}

func base64Decode(s string) ([]byte, error) {
    // Pad to proper length
    switch len(s) % 4 {
    case 2:
        s += "=="
    case 3:
        s += "="
    }
    return base64.URLEncoding.DecodeString(s)
}

func createJWT(claims JWTClaims, secret string) (string, error) {
    header := JWTHeader{Alg: "HS256", Typ: "JWT"}
    headerJSON, _ := json.Marshal(header)
    claimsJSON, _ := json.Marshal(claims)

    headerEnc := base64Encode(headerJSON)
    claimsEnc := base64Encode(claimsJSON)

    // Sign
    message := headerEnc + "." + claimsEnc
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    signature := base64Encode(mac.Sum(nil))

    return message + "." + signature, nil
}

func validateJWT(token string, secret string) (*JWTClaims, error) {
    parts := strings.Split(token, ".")
    if len(parts) != 3 {
        return nil, fmt.Errorf("invalid token format")
    }

    // Verify signature
    message := parts[0] + "." + parts[1]
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    expectedSig := base64Encode(mac.Sum(nil))

    if !hmac.Equal([]byte(parts[2]), []byte(expectedSig)) {
        return nil, fmt.Errorf("invalid signature")
    }

    // Parse claims
    claimsJSON, err := base64Decode(parts[1])
    if err != nil {
        return nil, err
    }

    var claims JWTClaims
    if err := json.Unmarshal(claimsJSON, &claims); err != nil {
        return nil, err
    }

    // Check expiration
    if time.Now().Unix() > claims.ExpiresAt {
        return nil, fmt.Errorf("token expired")
    }

    return &claims, nil
}

func main() {
    secret := "my-secret-key"

    // Generate Token
    claims := JWTClaims{
        UserID:   1,
        Role:     "admin",
        ExpiresAt: time.Now().Add(1 * time.Hour).Unix(),
    }

    token, _ := createJWT(claims, secret)
    fmt.Printf("JWT: %s\n", token)

    // Validate Token
    validated, err := validateJWT(token, secret)
    if err != nil {
        fmt.Printf("Validation failed: %v\n", err)
    } else {
        fmt.Printf("Validation succeeded: user=%d, role=%s\n", validated.UserID, validated.Role)
    }
}
85 logic lines (exceeds 40-line limit, display only)
💡 Tip: In a production environment, please use a mature JWT library (github.com/golang-jwt/jwt/v5) instead of implementing it manually. The code above is intended solely to illustrate how JWTs work. Third-party libraries handle more edge cases (such as token types, key rotation, and standard claims verification).



4. Access Token + Refresh Token

▶ Example: Two-Token Authentication

GO 📖 Display only
package main

import (
    "crypto/rand"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
    "time"
)

// ---------- Token Service ----------

type TokenService struct {
    secret     string
    refreshTTL time.Duration
    // Store refresh tokens (use Redis in production)
    refreshTokens map[string]int // token → userID
    mu            sync.RWMutex
}

func NewTokenService(secret string) *TokenService {
    return &TokenService{
        secret:         secret,
        refreshTTL:     7 * 24 * time.Hour,
        refreshTokens:  make(map[string]int),
    }
}

func (s *TokenService) GenerateTokens(userID int, role string) (accessToken, refreshToken string, err error) {
    // Access Token (expires in 15 minutes)
    accessClaims := JWTClaims{
        UserID:    userID,
        Role:      role,
        ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
    }
    accessToken, err = createJWT(accessClaims, s.secret)
    if err != nil {
        return "", "", err
    }

    // Refresh Token (random string, expires in 7 days)
    bytes := make([]byte, 32)
    rand.Read(bytes)
    refreshToken = hex.EncodeToString(bytes)

    s.mu.Lock()
    s.refreshTokens[refreshToken] = userID
    s.mu.Unlock()

    return accessToken, refreshToken, nil
}

func (s *TokenService) RefreshAccessToken(refreshToken string) (string, error) {
    s.mu.RLock()
    userID, exists := s.refreshTokens[refreshToken]
    s.mu.RUnlock()

    if !exists {
        return "", fmt.Errorf("invalid refresh token")
    }

    // Generate new Access Token
    claims := JWTClaims{
        UserID:    userID,
        Role:      "user",
        ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
    }
    return createJWT(claims, s.secret)
}

func (s *TokenService) RevokeRefreshToken(refreshToken string) {
    s.mu.Lock()
    delete(s.refreshTokens, refreshToken)
    s.mu.Unlock()
}

// ---------- Auth Handler ----------

type AuthHandler struct {
    tokenSvc *TokenService
}

type loginRequest struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

type tokenResponse struct {
    AccessToken  string `json:"access_token"`
    RefreshToken string `json:"refresh_token"`
    TokenType    string `json:"token_type"`
    ExpiresIn    int64  `json:"expires_in"`
}

func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
    var req loginRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
        return
    }

    // Verify username and password (use bcrypt in production)
    if req.Username != "admin" || req.Password != "password" {
        writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid credentials"})
        return
    }

    accessToken, refreshToken, err := h.tokenSvc.GenerateTokens(1, "admin")
    if err != nil {
        writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "token generation failed"})
        return
    }

    writeJSON(w, http.StatusOK, tokenResponse{
        AccessToken:  accessToken,
        RefreshToken: refreshToken,
        TokenType:    "Bearer",
        ExpiresIn:    900, // 15 minutes
    })
}

func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
    var req struct {
        RefreshToken string `json:"refresh_token"`
    }
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
        return
    }

    accessToken, err := h.tokenSvc.RefreshAccessToken(req.RefreshToken)
    if err != nil {
        writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid refresh token"})
        return
    }

    writeJSON(w, http.StatusOK, map[string]string{"access_token": accessToken})
}

func main() {
    tokenSvc := NewTokenService("my-secret-key")
    authHandler := &AuthHandler{tokenSvc: tokenSvc}

    mux := http.NewServeMux()
    mux.HandleFunc("POST /login", authHandler.Login)
    mux.HandleFunc("POST /refresh", authHandler.Refresh)

    fmt.Println("Auth service listening on :8080")
    http.ListenAndServe(":8080", mux)
}

func writeJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}
124 logic lines (exceeds 40-line limit, display only)
100%
sequenceDiagram
    participant Client
    participant API as API Gateway
    participant Auth as Auth Service

    Client->>API: POST /login (username, password)
    API->>Auth: Verify credentials
    Auth-->>API: access_token (15min) + refresh_token (7d)
    API-->>Client: Return dual tokens

    Client->>API: GET /orders (Bearer access_token)
    API->>API: Validate access_token
    API-->>Client: 200 OK

    Client->>API: GET /orders (access_token expired)
    API-->>Client: 401 Unauthorized

    Client->>API: POST /refresh (refresh_token)
    API->>Auth: Validate refresh_token
    Auth-->>API: New access_token
    API-->>Client: Return new access_token


5. RBAC Permissions Middleware

▶ Example: RBAC Implementation

GO 📖 Display only
package main

import (
    "context"
    "encoding/json"
    "net/http"
)

type UserRole سلسلة

const (
    RoleAdmin UserRole = "admin"
    RoleUser  UserRole = "user"
    RoleGuest UserRole = "guest"
)

// Permission definitions
var rolePermissions = map[UserRole][]سلسلة{
    RoleAdmin: {"read:users", "write:users", "delete:users", "read:orders", "write:orders"},
    RoleUser:  {"read:orders", "write:orders"},
    RoleGuest: {"read:products"},
}

type Claims struct {
    UserID int
    Role   UserRole
}

// Get claims from Context
func GetClaims(r *http.Request) *Claims {
    claims, _ := r.Context().Value("claims").(*Claims)
    return claims
}

// RBAC وسيط
func RequirePermission(permission سلسلة) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            claims := GetClaims(r)
            if claims == nil {
                writeError(w, http.StatusUnauthorized, "not authenticated")
                return
            }

            permissions, exists := rolePermissions[claims.Role]
            if !exists {
                writeError(w, http.StatusForbidden, "no permissions defined for role")
                return
            }

            // Check permission
            hasPermission := false
            for _, p := range permissions {
                if p == permission {
                    hasPermission = true
                    break
                }
            }

            if !hasPermission {
                writeError(w, http.StatusForbidden, "insufficient permissions")
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

// ---------- Route registration ----------

func setupRoutes() http.Handler {
    mux := http.NewServeMux()

    // Public endpoints
    mux.HandleFunc("POST /login", loginHandler)

    // Authenticated endpoints
    mux.Handle("GET /api/orders", RequirePermission("read:orders")(authMiddleware(http.HandlerFunc(listOrders))))
    mux.Handle("POST /api/orders", RequirePermission("write:orders")(authMiddleware(http.HandlerFunc(createOrder))))
    mux.Handle("DELETE /api/users/{id}", RequirePermission("delete:users")(authMiddleware(http.HandlerFunc(deleteUser))))

    return mux
}
61 logic lines (exceeds 40-line limit, display only)

(2) Middleware Pipeline

TEXT 📖 Display only
Request → Auth Middleware → RBAC Middleware → Handler
              |                   |
         Verify JWT Token    Check Role permissions
         Inject Claims       User has permission?
Level وسيط Responsibilities
1 Recovery Catch panic, return 500
2 Logging Log طلب duration
3 CORS Handle Cross-Origin Requests
4 Auth Verify JWT, inject user information
5 RBAC Check Role Permissions
6 RateLimit Token Bucket Throttling
7 Handler Business Logic


6. Token Bucket Throttling

▶ Example: Token Bucket Implementation

GO 📖 Display only
package main

import (
    "net/http"
    "sync"
    "time"
)

type TokenBucket struct {
    mu         sync.Mutex
    tokens     float64
    maxTokens  float64
    refillRate float64
    lastRefill time.Time
}

func NewTokenBucket(rate float64, burst int) *TokenBucket {
    return &TokenBucket{
        tokens:     float64(burst),
        maxTokens:  float64(burst),
        refillRate: rate,
        lastRefill: time.Now(),
    }
}

func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()

    // Refill tokens
    now := time.Now()
    elapsed := now.Sub(tb.lastRefill).Seconds()
    tb.tokens = min(tb.tokens+elapsed*tb.refillRate, tb.maxTokens)
    tb.lastRefill = now

    // Take a token
    if tb.tokens >= 1 {
        tb.tokens--
        return true
    }
    return false
}

func min(a, b float64) float64 {
    if a < b {
        return a
    }
    return b
}

// IP rate limiter
type IPRateLimiter struct {
    mu       sync.RWMutex
    buckets  map[string]*TokenBucket
    rate     float64
    burst    int
}

func NewIPRateLimiter(rate float64, burst int) *IPRateLimiter {
    return &IPRateLimiter{
        buckets: make(map[string]*TokenBucket),
        rate:    rate,
        burst:   burst,
    }
}

func (rl *IPRateLimiter) GetBucket(ip string) *TokenBucket {
    rl.mu.Lock()
    defer rl.mu.Unlock()

    bucket, exists := rl.buckets[ip]
    if !exists {
        bucket = NewTokenBucket(rl.rate, rl.burst)
        rl.buckets[ip] = bucket
    }
    return bucket
}

// RateLimiterMiddleware
func RateLimiterMiddleware(rate float64, burst int) Middleware {
    limiter := NewIPRateLimiter(rate, burst)

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ip := r.RemoteAddr
            bucket := limiter.GetBucket(ip)

            if !bucket.Allow() {
                w.Header().Set("Retry-After", "1")
                writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}
78 logic lines (exceeds 40-line limit, display only)

7. Complete Example: API Gateway Middleware Pipeline

▶ Example: Full Pipeline

GO 📖 Display only
// api_gateway.go
package main

import (
    "context"
    "encoding/json"
    "log"
    "net/http"
    "os"
    "os/signal"
    "strings"
    "syscall"
    "time"
)

// ---------- Type definitions ----------

type Middleware func(http.Handler) http.Handler

type Claims struct {
    UserID int
    Role   سلسلة
}

// ---------- Middleware ----------

// Recovery
func Recovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("[PANIC] %v", err)
                writeError(w, http.StatusInternalServerError, "internal خطأ")
            }
        }()
        next.ServeHTTP(w, r)
    })
}

// Logging
func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
        next.ServeHTTP(w, r)
        log.Printf("[%s] %s → %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// CORS
func CORS(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// Auth (JWT authentication)
func Auth(secret سلسلة) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authHeader := r.Header.Get("Authorization")
            if !strings.HasPrefix(authHeader, "Bearer ") {
                writeError(w, http.StatusUnauthorized, "missing token")
                return
            }

            token := authHeader[7:]
            claims, err := validateJWT(token, secret)
            if err != nil {
                writeError(w, http.StatusUnauthorized, err.Error())
                return
            }

            ctx := context.WithValue(r.Context(), "claims", &Claims{
                UserID: claims.UserID,
                Role:   claims.Role,
            })
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// RBAC
func RequireRole(roles ...سلسلة) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            claims, ok := r.Context().Value("claims").(*Claims)
            if !ok {
                writeError(w, http.StatusUnauthorized, "not authenticated")
                return
            }

            for _, role := range roles {
                if claims.Role == role {
                    next.ServeHTTP(w, r)
                    return
                }
            }

            writeError(w, http.StatusForbidden, "insufficient permissions")
        })
    }
}

// Rate Limiter
var rateLimiter = NewIPRateLimiter(10, 20) // 10 req/s, burst 20

func RateLimit(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ip := r.RemoteAddr
        bucket := rateLimiter.GetBucket(ip)
        if !bucket.Allow() {
            writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
            return
        }
        next.ServeHTTP(w, r)
    })
}

// ---------- Chain ----------

func Chain(h http.Handler, mws ...Middleware) http.Handler {
    for i := len(mws) - 1; i >= 0; i-- {
        h = mws[i](h)
    }
    return h
}

// ---------- Handlers ----------

type OrderHandler struct{}

func (h *OrderHandler) ListOrders(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[سلسلة]سلسلة{"orders": "list"})
}

func (h *OrderHandler) CreateOrder(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusCreated, map[سلسلة]سلسلة{"order": "created"})
}

type AdminHandler struct{}

func (h *AdminHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[سلسلة]سلسلة{"deleted": "user"})
}

// ---------- Utility functions ----------

func writeJSON(w http.ResponseWriter, حالة int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(حالة)
    json.NewEncoder(w).Encode(data)
}

func writeError(w http.ResponseWriter, حالة int, msg سلسلة) {
    writeJSON(w, حالة, map[سلسلة]سلسلة{"خطأ": msg})
}

// ---------- Main ----------

func main() {
    jwtSecret := "super-secret-key"
    orders := &OrderHandler{}

    mux := http.NewServeMux()

    // Public endpoint
    mux.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) {
        writeJSON(w, http.StatusOK, map[سلسلة]سلسلة{"token": "login-token"})
    })

    // Authenticated + rate-limited endpoints
    mux.Handle("GET /api/orders", Chain(
        http.HandlerFunc(orders.ListOrders),
        Auth(jwtSecret),
        RateLimit,
    ))

    mux.Handle("POST /api/orders", Chain(
        http.HandlerFunc(orders.CreateOrder),
        Auth(jwtSecret),
        RateLimit,
    ))

    // Admin endpoints (Auth + RBAC + RateLimit)
    mux.Handle("DELETE /api/users/{id}", Chain(
        http.HandlerFunc((&AdminHandler{}).DeleteUser),
        Auth(jwtSecret),
        RequireRole("admin"),
        RateLimit,
    ))

    // Global وسيط (Recovery → Logging → CORS → routes)
    app := Chain(mux, Recovery, Logging, CORS)

    خادم := &http.Server{Addr: ":8080", Handler: app}

    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
        <-sigCh
        log.Println("Shutting down...")
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        خادم.Shutdown(ctx)
    }()

    log.Println("API Gateway listening on :8080")
    if err := خادم.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
}
164 logic lines (exceeds 40-line limit, display only)
🔥 Common Mistake: The order of middleware is critical. Recovery must be the outermost layer (to catch all panics). Auth and RBAC must come before RateLimit—rate limiting should apply to unauthenticated requests as well, but unauthenticated users should not be allowed to consume tokens first. General order: Recovery → Logging → CORS → Auth → RBAC → RateLimit → Handler.


❓ FAQ

Q How is JWT implemented?
A A JWT consists of three parts: Header (algorithm + type), Payload (claims), and Signature. Go recommends using github.com/golang-jwt/jwt/v5. Access tokens are short-lived (15 minutes), while refresh tokens are long-lived (7 days) and stored on the server.
Q What is the difference between an Access Token and a Refresh Token?
A An Access Token is used for API authentication; it is valid for a short period (15 minutes) and contains user information and permissions. A Refresh Token is used to obtain a new Access Token; it is valid for a long period (7 days) and is stored on the server. When the Access Token expires, the client uses the Refresh Token to automatically obtain a new Access Token without requiring the user to log in again.
Q How is RBAC implemented?
A RBAC (Role-Based Access Control) assigns a set of permissions to each role, and the middleware checks whether the current user's role has the permissions required for the request. Implementation: rolePermissions[role] = []permission + the middleware checks permission in rolePermissions[claims.Role].
Q Which rate-limiting algorithm should I choose?
A The token bucket is the most commonly used—it allows for bursts and enables control over the average rate. Implementation: Refill the bucket with rate tokens per second, with a burst limit. Each request consumes one token; if there are not enough tokens, a 429 error is returned. Suitable for API rate limiting.
Q How can I prevent the recovery middleware from suppressing panic messages?
A Log the full stack trace in the recovery process, then return it using a custom error type. debug.Stack() prints the stack trace. Use the ERROR log level. In production, do not return the stack trace to the client—log it only on the server.
Q What is the execution order of the middleware pipeline?
A The onion model. Chain(h, A, B, C) → The request passes through A → B → C → Handler → C → B → A. Registration order = from the outermost layer to the innermost layer. Recovery is at the outermost layer (captures panics thrown by any layer), and Handler is at the innermost layer (business logic).
Q How is a JWT revoked?
A A JWT is stateless—once issued, it cannot be revoked until it expires. Solution: (1) Short-term token + refresh token (the refresh token can be revoked); (2) Blacklist (store revoked JWT IDs in Redis); (3) Version number (increment the version number when a user changes their password; old tokens become invalid).

📖 Summary


📝 Exercises

  1. Basic (Difficulty ⭐): Implement a simple authentication middleware. Create a /api/protected endpoint that requires the request header to include Authorization: Bearer token123. If the token is valid, return {"message": "access granted"}; otherwise, return a 401 error.

  2. Advanced (Difficulty ⭐⭐): Implement a complete JWT authentication system. Requirements: (1) POST /register for registration (password encrypted with bcrypt); (2) POST /login for login (returns access_token + refresh_token); (3) An Auth middleware that validates the access_token for all /api/* endpoints; (4) POST /refresh to exchange the refresh_token for a new access_token; (5) POST /logout to revoke the refresh_token.

  3. Challenge (Difficulty ⭐⭐⭐): Implement a blog management API with RBAC and rate limiting. Requirements: (1) Three roles: admin, editor, and reader; (2) admin: full permissions; (3) editor: create, edit, and delete their own posts; (4) reader: read-only access; (5) Each role has different rate limits; (6) All endpoints must pass through the following middleware pipeline: Recovery → Logging → Auth → RBAC → RateLimit → Handler; (7) Use -race to verify concurrency safety.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏