🔐 Adding JWT Authentication to Your Golang API with Gin

If you're building APIs with Go, sooner or later you'll need to authenticate users. One of the most popular and scalable ways to do this is by using JWT (JSON Web Tokens).

In this guide, we'll walk through:

  • What JWT is and why it's useful

  • How to implement JWT-based authentication in Go using Gin

  • How to protect your API routes

  • Testing everything using Postman


🧠 What is JWT?

JWT (JSON Web Token) is a compact and stateless way of securely transmitting information between two parties. It consists of:

  • Header – contains algorithm & token type

  • Payload – contains claims like user ID, roles, etc.

  • Signature – verifies the token hasn’t been tampered with

Example JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

It's perfect for REST APIs because it doesn’t require session state — just validate the token on each request.


⚙️ Project Setup

📦 Step 1: Install Required Packages

go mod init jwt-auth-example
go get github.com/gin-gonic/gin
go get github.com/golang-jwt/jwt/v5

🧱 Step 2: Define the Main Structure

Create a file called main.go and paste the following:

package main

import (
    "fmt"
    "net/http"
    "time"

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

var jwtSecret = []byte("your_secret_key") // Change this to a secure value

// JWT Claims structure
type Claims struct {
    Username string `json:"username"`
    jwt.RegisteredClaims
}

func main() {
    r := gin.Default()

    r.POST("/login", loginHandler)
    r.GET("/protected", authMiddleware(), protectedHandler)

    r.Run(":8080")
}

🔑 Step 3: JWT Token Generation (Login Route)

func loginHandler(c *gin.Context) {
    var body struct {
        Username string `json:"username"`
        Password string `json:"password"`
    }

    if err := c.BindJSON(&body); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
        return
    }

    // Simple check (in real apps, validate from DB)
    if body.Username != "admin" || body.Password != "password" {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
        return
    }

    expirationTime := time.Now().Add(30 * time.Minute)
    claims := &Claims{
        Username: body.Username,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(expirationTime),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err := token.SignedString(jwtSecret)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Token generation failed"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"token": tokenString})
}

🛡️ Step 4: Auth Middleware (Protect Routes)

func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        if tokenString == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing Authorization header"})
            c.Abort()
            return
        }

        claims := &Claims{}
        token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
            return jwtSecret, nil
        })

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

        // Store the username in context for later use
        c.Set("username", claims.Username)
        c.Next()
    }
}

✅ Step 5: Protected Route Example

func protectedHandler(c *gin.Context) {
    username := c.MustGet("username").(string)
    c.JSON(http.StatusOK, gin.H{
        "message": fmt.Sprintf("Welcome, %s! You have access to protected data.", username),
    })
}

🧪 Testing with Postman

  1. POST /login

    • Body:

        {
          "username": "admin",
          "password": "password"
        }
      
    • ✅ Response will include token

  2. GET /protected

    • Add header:

        Authorization: <paste-your-token-here>
      
    • ✅ Should return welcome message if token is valid

🛠️ You can use Postman for testing, or install Thunder Client in VSCode.


🔗 Tools I Used

ToolPurpose
JetBrains GoLandAdvanced IDE for Go
PostmanTest APIs locally
Gin GonicWeb framework for Go
Udemy Go Security CourseLearn Go + JWT in depth

🧠 Final Thoughts

You’ve just added secure, token-based authentication to your Go API using JWT and Gin! This is the foundation of most modern backend systems — and you’ve built it from scratch.

Let me know in the comments if you’d like a follow-up on:

  • Refresh tokens

  • Storing tokens in cookies

  • Role-based access control

👉 Found this helpful? Subscribe for more posts on building secure, scalable backends with Golang & Python!


📢 Coming Next:

“Connecting Your Golang API to Redis: Fast Caching for Real-World APIs”


0
Subscribe to my newsletter

Read articles from NEELAM SAI KUMAR directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

NEELAM SAI KUMAR
NEELAM SAI KUMAR