JWT Authentication and Authorization with Golang: End-to-End Guide
User logs in: The user sends a POST request to the
/login
endpoint.API processes request: The API forwards this request to the
Auth Controller
for login processing.Check credentials: The
Auth Controller
checks the user credentials in the mock database.Generate JWT: If credentials are valid, the
Auth Controller
callsToken Utils
to generate a JWT.Return JWT: The generated JWT is returned to the API and then back to the user.
Access protected resource: The user sends a GET request to the
/auth/protected
endpoint with the JWT in the Authorization header.Validate JWT: The API uses
JWT Middleware
to validate the JWT by callingToken Utils
.Claims validation:
Token Utils
returns the claims toJWT Middleware
, which allows the request to proceed.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
Password Hashing: Use libraries like
bcrypt
to hash passwords instead of storing them in plaintext.Token Expiry: Handle token expiry gracefully by providing a refresh mechanism.
Role-Based Authorization: Extend the middleware to check user roles.
Secure Secret Key: Use environment variables to manage sensitive keys.