How to Build a REST API with Authentication in Go

Web
Use the standard `net/http` package combined with a middleware pattern to handle authentication, typically by validating a Bearer token or session cookie before the request reaches your handler.

Use the standard net/http package combined with a middleware pattern to handle authentication, typically by validating a Bearer token or session cookie before the request reaches your handler. You should extract the authentication logic into a reusable middleware function that checks credentials and sets context values for the authenticated user.

Here is a practical implementation using JWT (JSON Web Tokens) for stateless authentication. First, create a middleware that validates the token and injects the user ID into the request context:

package main

import (
	"context"
	"net/http"
	"strings"
	"time"

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

const secretKey = "your-super-secret-key-change-in-production"

type UserClaims struct {
	UserID string `json:"user_id"`
	jwt.RegisteredClaims
}

func authMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		authHeader := r.Header.Get("Authorization")
		if authHeader == "" {
			http.Error(w, "Missing Authorization header", http.StatusUnauthorized)
			return
		}

		tokenString := strings.TrimPrefix(authHeader, "Bearer ")
		if tokenString == authHeader {
			http.Error(w, "Invalid token format", http.StatusUnauthorized)
			return
		}

		claims := &UserClaims{}
		token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
			return []byte(secretKey), nil
		})

		if err != nil || !token.Valid {
			http.Error(w, "Invalid or expired token", http.StatusUnauthorized)
			return
		}

		// Attach user ID to context for downstream handlers
		ctx := context.WithValue(r.Context(), "userID", claims.UserID)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

Next, define your protected handler and wire it up with the middleware in your main function. This example shows how to retrieve the user ID from the context inside the handler:

func protectedHandler(w http.ResponseWriter, r *http.Request) {
	userID, ok := r.Context().Value("userID").(string)
	if !ok {
		http.Error(w, "User not found in context", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"message": "Access granted", "user_id": "` + userID + `"}`))
}

func main() {
	mux := http.NewServeMux()

	// Public endpoint
	mux.HandleFunc("/public", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Public data"))
	})

	// Protected endpoint wrapped with auth middleware
	mux.Handle("/protected", authMiddleware(http.HandlerFunc(protectedHandler)))

	http.ListenAndServe(":8080", mux)
}

To test this, generate a valid token using a tool like jwt.io or a script, then send a request with the Authorization: Bearer <token> header. If the token is missing or invalid, the middleware returns a 401 error immediately. This approach keeps your business logic clean by separating security concerns from resource handling. Remember to replace the secretKey with a secure, randomly generated value stored in environment variables for production deployments.