JWT Authentication and Authorization with Golang: End-to-End Guide

Here's a brief explanation of the diagram:

  1. User logs in: The user sends a POST request to the /login endpoint.

  2. API processes request: The API forwards this request to the Auth Controller for login processing.

  3. Check credentials: The Auth Controller checks the user credentials in the mock database.

  4. Generate JWT: If credentials are valid, the Auth Controller calls Token Utils to generate a JWT.

  5. Return JWT: The generated JWT is returned to the API and then back to the user.

  6. Access protected resource: The user sends a GET request to the /auth/protected endpoint with the JWT in the Authorization header.

  7. Validate JWT: The API uses JWT Middleware to validate the JWT by calling Token Utils.

  8. Claims validation: Token Utils returns the claims to JWT Middleware, which allows the request to proceed.

  9. Resource access: The API processes the request and returns the protected resource to the user.

This guide provides a comprehensive walkthrough for implementing JSON Web Token (JWT) authentication and authorization in a Golang application. We’ll cover both the backend setup and how to secure API endpoints with JWT.


Prerequisites

  • Basic understanding of Golang.

  • Installed Go environment (1.18 or later).

  • Familiarity with RESTful APIs.

  • A tool for testing APIs (e.g., Postman or curl).


1. Setting Up the Project

Initialize the Go Project

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

Directory Structure

jwt-auth-example/
├── main.go
├── controllers/
│   └── auth_controller.go
├── middlewares/
│   └── jwt_middleware.go
├── models/
│   └── user.go
├── utils/
    └── token_utils.go

2. Creating the User Model

In models/user.go:

package models

type User struct {
	ID       int    `json:"id"`
	Username string `json:"username"`
	Password string `json:"password"`
}

// Mock database for demo purposes
var Users = []User{
	{ID: 1, Username: "user1", Password: "password1"},
	{ID: 2, Username: "user2", Password: "password2"},
}

3. Generating JWT Tokens

In utils/token_utils.go:

package utils

import (
	"time"

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

var jwtKey = []byte("your_secret_key")

func GenerateJWT(username string) (string, error) {
	claims := &jwt.RegisteredClaims{
		Subject:   username,
		ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtKey)
}

func ValidateJWT(tokenString string) (*jwt.RegisteredClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
		return jwtKey, nil
	})

	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*jwt.RegisteredClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, jwt.ErrTokenMalformed
}

4. Authentication Controller

In controllers/auth_controller.go:

package controllers

import (
	"jwt-auth-example/models"
	"jwt-auth-example/utils"

	"github.com/gin-gonic/gin"
	"net/http"
)

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

	for _, user := range models.Users {
		if user.Username == input.Username && user.Password == input.Password {
			token, err := utils.GenerateJWT(user.Username)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not generate token"})
				return
			}
			c.JSON(http.StatusOK, gin.H{"token": token})
			return
		}
	}

	c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
}

5. JWT Middleware

In middlewares/jwt_middleware.go:

package middlewares

import (
	"jwt-auth-example/utils"

	"github.com/gin-gonic/gin"
	"net/http"
)

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

		claims, err := utils.ValidateJWT(tokenString)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		c.Set("username", claims.Subject)
		c.Next()
	}
}

6. Main Application

In main.go:

package main

import (
	"jwt-auth-example/controllers"
	"jwt-auth-example/middlewares"

	"github.com/gin-gonic/gin"
)

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

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

	auth := r.Group("/auth")
	auth.Use(middlewares.JWTAuthMiddleware())
	{
		auth.GET("/protected", func(c *gin.Context) {
			username := c.MustGet("username").(string)
			c.JSON(200, gin.H{"message": "Welcome " + username})
		})
	}

	r.Run(":8080")
}

7. Testing the API

Login Endpoint

  • Endpoint: POST /login

  • Request Body:

{
	"username": "user1",
	"password": "password1"
}
  • Response:

{
	"token": "<JWT_TOKEN>"
}

Protected Endpoint

  • Endpoint: GET /auth/protected

  • Headers:

    • Authorization: <JWT_TOKEN>

  • Response:

{
	"message": "Welcome user1"
}

8. Enhancements

  1. Password Hashing: Use libraries like bcrypt to hash passwords instead of storing them in plaintext.

  2. Token Expiry: Handle token expiry gracefully by providing a refresh mechanism.

  3. Role-Based Authorization: Extend the middleware to check user roles.

  4. Secure Secret Key: Use environment variables to manage sensitive keys.


Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete