DEV Community

Cover image for Building a Lightweight JWT Authentication Middleware for Go Gin Applications
Daniel Keya
Daniel Keya

Posted on

Building a Lightweight JWT Authentication Middleware for Go Gin Applications

Building a Lightweight JWT Authentication Middleware for Go Gin Applications

Authentication is the backbone of secure web applications. Today, we'll create a clean, production-ready JWT authentication middleware for Go applications using the Gin framework.

🎯 What We're Building

Our middleware will handle:

  • πŸ” JWT token validation
  • πŸ“ Bearer token support
  • πŸ‘€ User context extraction
  • βš™οΈ Environment configuration
  • πŸ§ͺ Comprehensive testing

πŸ—οΈ Project Setup

Let's start by setting up our project structure:

mkdir authmiddleware && cd authmiddleware
go mod init authmiddleware
Enter fullscreen mode Exit fullscreen mode

Install dependencies:

go get github.com/gin-gonic/gin
go get github.com/golang-jwt/jwt/v5
go get github.com/joho/godotenv
Enter fullscreen mode Exit fullscreen mode

πŸ“ Project Structure

authmiddleware/
β”œβ”€β”€ config/
β”‚   └── config.go
β”œβ”€β”€ middlewares/
β”‚   └── authmiddleware.go
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ auth_middleware_test.go
β”‚   └── config_test.go
β”œβ”€β”€ docs/
β”œβ”€β”€ .env.example
β”œβ”€β”€ go.mod
└── README.md
Enter fullscreen mode Exit fullscreen mode

βš™οΈ Configuration Management

First, let's create our configuration handler in config/config.go:

package config

import (
    "log"
    "os"
    "github.com/joho/godotenv"
)

type Config struct {
    Port      string
    JWTSecret string
}

var AppConfig *Config

func LoadEnv() {
    err := godotenv.Load()
    if err != nil {
        log.Println("No .env file found, using system environment")
    }

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    AppConfig = &Config{
        Port:      port,
        JWTSecret: os.Getenv("JWT_SECRET"),
    }

    if AppConfig.JWTSecret == "" {
        log.Fatal("JWT_SECRET environment variable is required")
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ” The Authentication Middleware

Now for the main event - our JWT middleware in middlewares/authmiddleware.go:

package middlewares

import (
    "net/http"
    "os"
    "strings"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
)

func AuthMiddleware() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        // Extract token from Authorization header
        authHeader := ctx.GetHeader("Authorization")
        if authHeader == "" {
            ctx.JSON(http.StatusUnauthorized, gin.H{
                "error": "Authorization header is required"
            })
            ctx.Abort()
            return
        }

        // Handle both "Bearer token" and direct token formats
        tokenString := strings.TrimSpace(authHeader)
        if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
            tokenString = strings.TrimSpace(tokenString[7:])
        }

        // Parse and validate JWT token
        token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
            if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, jwt.ErrSignatureInvalid
            }
            return []byte(os.Getenv("JWT_SECRET")), nil
        })

        if err != nil || !token.Valid {
            ctx.JSON(http.StatusUnauthorized, gin.H{
                "error": "Invalid or expired token"
            })
            ctx.Abort()
            return
        }

        // Extract claims and set context
        if claims, ok := token.Claims.(jwt.MapClaims); ok {
            ctx.Set("user_id", claims["user_id"])
            ctx.Set("email", claims["email"])
        }

        ctx.Next()
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸš€ Usage Example

Here's how to integrate the middleware into your application:

package main

import (
    "authmiddleware/config"
    "authmiddleware/middlewares"
    "github.com/gin-gonic/gin"
)

func main() {
    config.LoadEnv()

    r := gin.Default()

    // Public routes
    r.GET("/health", func(c *gin.Context) {
        c.JSON(200, gin.H{"status": "healthy"})
    })

    // Protected routes
    protected := r.Group("/api/v1")
    protected.Use(middlewares.AuthMiddleware())
    {
        protected.GET("/profile", getProfile)
        protected.POST("/data", postData)
    }

    r.Run(":" + config.AppConfig.Port)
}

func getProfile(c *gin.Context) {
    userID := c.GetString("user_id")
    email := c.GetString("email")

    c.JSON(200, gin.H{
        "user_id": userID,
        "email":   email,
        "message": "Profile retrieved successfully"
    })
}
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Testing Our Middleware

Testing is crucial! Here's our test suite in tests/auth_middleware_test.go:

package tests

import (
    "net/http"
    "net/http/httptest"
    "os"
    "testing"
    "time"

    "authmiddleware/middlewares"
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
)

func TestMain(m *testing.M) {
    gin.SetMode(gin.TestMode)
    os.Setenv("JWT_SECRET", "test-secret-key")
    os.Exit(m.Run())
}

func createTestToken(userID, email string) string {
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "user_id": userID,
        "email":   email,
        "exp":     time.Now().Add(time.Hour).Unix(),
    })
    tokenString, _ := token.SignedString([]byte("test-secret-key"))
    return tokenString
}

func TestAuthMiddleware_ValidToken(t *testing.T) {
    router := gin.New()
    router.Use(middlewares.AuthMiddleware())
    router.GET("/test", func(c *gin.Context) {
        userID := c.GetString("user_id")
        email := c.GetString("email")
        c.JSON(200, gin.H{"user_id": userID, "email": email})
    })

    token := createTestToken("123", "test@example.com")
    req := httptest.NewRequest("GET", "/test", nil)
    req.Header.Set("Authorization", "Bearer "+token)

    w := httptest.NewRecorder()
    router.ServeHTTP(w, req)

    if w.Code != http.StatusOK {
        t.Errorf("Expected status 200, got %d", w.Code)
    }
}

func TestAuthMiddleware_MissingToken(t *testing.T) {
    router := gin.New()
    router.Use(middlewares.AuthMiddleware())
    router.GET("/test", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "success"})
    })

    req := httptest.NewRequest("GET", "/test", nil)
    w := httptest.NewRecorder()
    router.ServeHTTP(w, req)

    if w.Code != http.StatusUnauthorized {
        t.Errorf("Expected status 401, got %d", w.Code)
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ”’ Security Best Practices

When implementing JWT authentication:

  1. Strong Secrets: Use cryptographically secure JWT secrets
  2. Token Expiration: Always set expiration times
  3. Signing Method Validation: Verify expected algorithms
  4. HTTPS Only: Never transmit tokens over HTTP
  5. Error Handling: Don't leak sensitive information

πŸƒβ€β™‚οΈ Running Tests

Test your implementation:

# Run all tests
go test ./tests/...

# With coverage
go test -cover ./tests/...

# Verbose output
go test -v ./tests/...
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Environment Setup

Create .env.example:

PORT=8080
JWT_SECRET=your-super-secret-jwt-key-here
Enter fullscreen mode Exit fullscreen mode

πŸŽ‰ What We've Accomplished

Our middleware now provides:

βœ… JWT token validation

βœ… Flexible token format support

βœ… User context extraction

βœ… Comprehensive error handling

βœ… Production-ready code

βœ… Full test coverage

πŸš€ Next Steps

Consider extending with:

  • Role-based authorization
  • Token refresh mechanism
  • Rate limiting
  • Audit logging
  • Multi-tenant support

πŸ’‘ Key Takeaways

Building your own auth middleware gives you:

  • Complete control over authentication flow
  • Better understanding of JWT security
  • Lightweight, dependency-minimal solution
  • Easy customization for business needs

πŸ“š Source Code

πŸ”— Complete source code: GitHub Repository

⭐ Star the repo if this helped you! ⭐

What authentication challenges have you faced in your Go applications? Share your experiences in the comments below!

Connect with me:

  • πŸ™ GitHub: @keyadaniel56 - Follow for more Go tutorials!
  • πŸ’¬ Let's discuss Go best practices and build amazing things together!

Found this helpful? Give it a ❀️ and share with fellow developers!

Top comments (0)