programming

10 Creating Authentication Middleware Using JWT with Httprouter in Golang

In modern application development, authentication is one of the most crucial components. JSON Web Tokens (JWT) is a popular method for handling token-based authentication. In this article, we will create an authentication middleware using JWT in Golang with the httprouter library from julienschmidt.

This article is designed for beginner programmers with detailed steps and explanations to make it easy to follow.


Prerequisites

  1. Basic understanding of Golang: You should have a basic understanding of Golang, including functions, structures, and modules.
  2. Golang installed: Ensure you have Go installed on your system.
  3. Required libraries: We will use the following additional libraries:
    • github.com/golang-jwt/jwt/v4 for managing JWT.
    • github.com/julienschmidt/httprouter for routing.

To install these libraries, use the following command:

go get github.com/golang-jwt/jwt/v4 github.com/julienschmidt/httprouter

Step 1: Creating the Project Structure

Create the following folder structure:

project-root/
├── main.go
├── middleware/
│   └── auth.go
├── handlers/
│   └── user.go
├── utils/
│   └── jwt.go

The main.go file serves as the entry point of the application, while the other folders organize various functions and middleware.


Step 2: Creating the Main File

Open main.go and add the following code:

package main

import (
	"fmt"
	"log"
	"net/http"
	"github.com/julienschmidt/httprouter"
	"project-root/middleware"
	"project-root/handlers"
)

func main() {
	router := httprouter.New()

	// Public endpoint without authentication
	router.GET("/public", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		fmt.Fprint(w, "Public endpoint does not require authentication!\n")
	})

	// Private endpoint using authentication middleware
	router.GET("/private", middleware.JWTAuth(handlers.PrivateHandler))

	log.Println("Server running at http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", router))
}

The above code provides a minimal application setup using middleware for private endpoints.


Step 3: Creating JWT Middleware

Open middleware/auth.go and add the following code:

package middleware

import (
	"net/http"
	"strings"
	"github.com/golang-jwt/jwt/v4"
	"project-root/utils"
	"github.com/julienschmidt/httprouter"
)

func JWTAuth(next httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		tokenString := extractToken(r)
		if tokenString == "" {
			http.Error(w, "Authorization header missing", http.StatusUnauthorized)
			return
		}

		// Validate token
		claims, err := utils.ValidateJWT(tokenString)
		if err != nil {
			http.Error(w, "Invalid token", http.StatusUnauthorized)
			return
		}

		// Proceed to the next handler
		next(w, r, ps)
	}
}

func extractToken(r *http.Request) string {
	bearer := r.Header.Get("Authorization")
	if bearer == "" || !strings.HasPrefix(bearer, "Bearer ") {
		return ""
	}
	return strings.TrimPrefix(bearer, "Bearer ")
}

This middleware extracts the token from the Authorization header, validates it, and proceeds to the next handler if the token is valid.


Step 4: Creating the Private Endpoint Handler

Open handlers/user.go and add the following code:

package handlers

import (
	"fmt"
	"net/http"
	"github.com/julienschmidt/httprouter"
)

func PrivateHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Welcome to the private endpoint! Your token is valid.\n")
}

This handler can only be accessed if the JWT middleware successfully validates the token.


Step 5: Creating Utility Functions for JWT

Open utils/jwt.go and add the following code:

package utils

import (
	"errors"
	"time"
	"github.com/golang-jwt/jwt/v4"
)

var secretKey = []byte("super_secret")

type CustomClaims struct {
	Username string `json:"username"`
	jwt.RegisteredClaims
}

func GenerateJWT(username string) (string, error) {
	claims := CustomClaims{
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(secretKey)
}

func ValidateJWT(tokenString string) (*CustomClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return secretKey, nil
	})
	if err != nil {
		return nil, err
	}

	claims, ok := token.Claims.(*CustomClaims)
	if !ok || !token.Valid {
		return nil, errors.New("invalid token")
	}

	return claims, nil
}

This file contains functions for creating and validating JWT tokens. GenerateJWT is used to create a token, while ValidateJWT checks its validity.


Step 6: Testing the Application

Run the application with the following command:

go run main.go

Test the endpoints using curl or Postman.

1. Public Endpoint (No Token Required)

curl http://localhost:8080/public

Response:

Public endpoint does not require authentication!

2. Private Endpoint (With Token)

First, generate a token using GenerateJWT (implement it in another app or manually for testing). Then, use the token to access the private endpoint:

curl -H "Authorization: Bearer <YOUR_TOKEN>" http://localhost:8080/private

If the token is valid, the response will be:

Welcome to the private endpoint! Your token is valid.

Conclusion

You have successfully created an authentication middleware using JWT in Golang with httprouter. With this approach, you can ensure that sensitive endpoints are only accessible to authenticated users. This middleware is also flexible for production applications.

Hopefully, this article helps you understand the basics of JWT in Golang. If you have any questions, feel free to ask or explore further!

comments powered by Disqus