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.