package httpauthjwt import ( "net/http" "path" "strings" "github.com/golang-jwt/jwt/v5" httpauthmw "code.nochebuena.dev/go/httpauth" ) // AuthMiddleware verifies the Bearer access token and injects uid + claims into // the request context via httpauth.SetTokenData. Downstream middleware // (EnrichmentMiddleware, AuthzMiddleware, ClaimsPermissionProvider) from // code.nochebuena.dev/go/httpauth reads them transparently. // // Accepts a Verifier — pass a Signer or a NewRSAPublicKeyVerifier depending on // whether the service issues tokens. // // Requests to publicPaths are skipped without token verification (wildcards // supported via path.Match). Returns 401 on missing, invalid, or expired tokens. func AuthMiddleware(verifier Verifier, publicPaths []string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for _, pattern := range publicPaths { if matched, _ := path.Match(pattern, r.URL.Path); matched { next.ServeHTTP(w, r) return } } authHeader := r.Header.Get("Authorization") if !strings.HasPrefix(authHeader, "Bearer ") { http.Error(w, "unauthorized", http.StatusUnauthorized) return } tokenStr := strings.TrimPrefix(authHeader, "Bearer ") token, err := verifier.Verify(tokenStr) if err != nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { http.Error(w, "unauthorized", http.StatusUnauthorized) return } uid, _ := claims["sub"].(string) if uid == "" { http.Error(w, "unauthorized", http.StatusUnauthorized) return } ctx := httpauthmw.SetTokenData(r.Context(), uid, map[string]any(claims)) next.ServeHTTP(w, r.WithContext(ctx)) }) } }