Understanding JWT Authentication in Go (Gin Framework)

Read Time:3 Minute, 24 Second

Introduction

Authentication is at the heart of any secure web application. Traditionally, developers have relied on server-side sessions to manage logged-in users. But as applications scale into microservices and stateless APIs, a modern approach is needed.

That’s where JWT (JSON Web Tokens) comes in. JWT allows us to authenticate users in a stateless, scalable, and secure way.

In this blog, we’ll:

  • Understand what JWT is and why it matters.
  • Learn how JWT works step-by-step.
  • Implement JWT authentication in Go using the Gin framework.
  • Explore the example repo: go-jwt-auth-use.

What is JWT?

JWT (JSON Web Token) is a compact way of securely transmitting information between two parties (usually client ↔ server).

It looks like this:

xxxxx.yyyyy.zzzzz

It has 3 parts:

  1. Header – contains algorithm & type.
  2. Payload – contains claims (data like username, exp).
  3. Signature – verifies the token’s integrity using a secret key.

Example payload:

{
  "username": "alice",
  "exp": 1694445200
}

When a token is signed, the server can later verify if it’s valid and untampered.

Why JWT for APIs?

  • Stateless – no need to store sessions on the server.
  • Fast – each request carries its identity (the token).
  • Cross-platform – can be used across mobile apps, web apps, microservices.

How JWT Authentication Works

  1. User registers → password is hashed and stored.
  2. User logs in → server generates a JWT with user details.
  3. Server sends back the JWT.
  4. Client stores it (localStorage, cookie, or memory).
  5. For every request → client sends token in Authorization header.
  6. Middleware validates the JWT before allowing access.

Project Structure

From the repo go-jwt-auth-use:

.
├── main.go
├── controllers/
│   └── auth.go
├── middlewares/
│   └── auth.go
├── models/
│   └── user.go
└── routes/
    └── auth.go

Implementation in Go Gin

1. Register User

func Register(c *gin.Context) {
    var input models.User
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Hash password before saving
    hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(input.Password), 10)
    input.Password = string(hashedPassword)

    models.DB.Create(&input)
    c.JSON(http.StatusOK, gin.H{"message": "user registered successfully"})
}

2. Login and Generate JWT

func Login(c *gin.Context) {
    var input models.User
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    var user models.User
    models.DB.Where("username = ?", input.Username).First(&user)

    // Verify password
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(input.Password)); err != nil {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
        return
    }

    // Create JWT
    claims := jwt.MapClaims{
        "username": user.Username,
        "exp":      time.Now().Add(time.Hour * 1).Unix(),
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, _ := token.SignedString([]byte("secret_key"))

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

3. Middleware to 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 token"})
            c.Abort()
            return
        }

        token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
            return []byte("secret_key"), nil
        })

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

        claims := token.Claims.(jwt.MapClaims)
        c.Set("username", claims["username"])
        c.Next()
    }
}

4. Protected Route Example

func ProtectedEndpoint(c *gin.Context) {
    username := c.GetString("username")
    c.JSON(http.StatusOK, gin.H{
        "message": "Welcome " + username,
    })
}

5. Routes Setup

r := gin.Default()

r.POST("/register", controllers.Register)
r.POST("/login", controllers.Login)

protected := r.Group("/api")
protected.Use(middlewares.AuthMiddleware())
{
    protected.GET("/posts", controllers.ProtectedEndpoint)
}

Running the Project

  1. Clone repo: git clone https://github.com/vickychhetri/go-jwt-auth-use.git cd go-jwt-auth-use go run main.go
  2. Register a user:
    POST /register with { "username": "vicky", "password": "vicky@12345" }
  3. Login:
    POST /login → copy the JWT.
  4. Access protected route:
    GET /api/posts with header: Authorization: <jwt-token>

Key Takeaways

  • JWT allows stateless authentication.
  • Always hash passwords before storing.
  • Always set expiration (exp) for tokens.
  • Keep your secret key safe (use environment variables in production).
  • Middleware ensures only valid tokens can access protected routes.

Repo

👉 Full source code is here: go-jwt-auth-use

Happy
Happy
100 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %