JWT auth middleware
Hub › Golang › Advanced › JWT auth middleware
Goal
Add JWT bearer token authentication. Protected endpoints reject unauthenticated requests with 401.
Prerequisites
Add the JWT library
bash
go get github.com/golang-jwt/jwt/v5Auth middleware
Create auth.go:
go
package main
import (
"context"
"log"
"net/http"
"os"
"strings"
"github.com/golang-jwt/jwt/v5"
)
var jwtSecret = func() []byte {
s := os.Getenv("JWT_SECRET")
if s == "" {
s = "dev-secret-do-not-use-in-production"
}
return []byte(s)
}()The auth middleware extracts the Authorization: Bearer <token> header, validates the JWT, and stores the claims in the request context:
go
func auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ah := r.Header.Get("Authorization")
if ah == "" || !strings.HasPrefix(ah, "Bearer ") {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
token, err := jwt.Parse(strings.TrimPrefix(ah, "Bearer "),
func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return jwtSecret, nil
})
if err != nil || !token.Valid {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}Login endpoint
Add an unauthenticated endpoint that returns a token. In production this would verify a username/password; for this tier a simple POST to /login with any body returns a token:
Create login.go:
go
package main
import (
"encoding/json"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
)
type loginRequest struct {
Username string `json:"username"`
}
type loginResponse struct {
Token string `json:"token"`
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
var req loginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": req.Username,
"exp": time.Now().Add(1 * time.Hour).Unix(),
})
signed, err := token.SignedString(jwtSecret)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(loginResponse{Token: signed})
}Wire protected routes
Update main.go to protect write endpoints with auth:
go
mux.HandleFunc("POST /login", handleLogin)
mux.HandleFunc("GET /items", handleList(store))
mux.HandleFunc("GET /items/{id}", handleGet(store))
mux.Handle("POST /items", auth(http.HandlerFunc(handleCreate(store))))Checkpoint
bash
go run .
# Get a token:
TOKEN=$(curl -s -X POST http://localhost:8080/login \
-d '{"username":"alice"}' | gojq -r '.token')
# Protected endpoint works with token:
curl -i -X POST http://localhost:8080/items \
-H "Authorization: Bearer $TOKEN" \
-d '{"name":"laptop"}'
# Protected endpoint fails without token:
curl -i -X POST http://localhost:8080/items
# → 401 UnauthorizedNext: Rate limiting — protect against abuse with per-IP rate limits.