Five programs, one app. Each serves identical routes with identical security options; the only difference is how that framework’s router is attached to the go-security chain. Because every leg of the chain is a func(http.Handler) http.Handler and securityhttp.NewHandler returns a single http.Handler, four of the five need no adapter at all.
These live in a separate Go module (replaced onto the parent) so Gin, Echo, chi, gorilla/mux, and Fiber never enter the library’s dependency graph — the root module stays standard library plus golang.org/x/crypto.For the full integration reference — support matrix, per-route middleware adapters, role-hierarchy caveats — see the Web Frameworks guide.

The integration story, end to end

  1. app.go holds everything the five programs share: two in-memory users (alice/ADMIN, bob/USER), a session store, cookies, and the securityhttp option list.
  2. Each main.go builds its framework’s router and registers four routes.
  3. The chain wraps the whole router — securityhttp.NewHandler(router, opts...) — so headers, session lookup, CSRF, and default-deny authorization run before the framework routes anything.
  4. Admin access is enforced inside each framework’s own route group, so both enforcement layers appear in one program.

Shared security options

Two deliberate choices. CSRF uses the double-submit-cookie repository instead of the session-backed synchronizer default, because the login POST happens before any session exists for a synchronizer token to live in. And there is no /admin rule here — that check belongs to the framework groups below. Route patterns are matched against the raw request path, before your framework parses path parameters, so write them as paths (/admin/**) and never as route templates (/users/:id, /users/{id}) — those strings never appear in a URL.

One line per framework

Then serve it like any handler (Fiber uses app.Listen instead):
frameworks.Principal(ctx) is just core.MustAuthentication(ctx).Name() — the authentication lives on the request context, so only the way each framework hands you that context differs.

Gin’s middleware adapter

Gin has gin.WrapH for handlers but nothing for middleware, so the Gin example carries a ten-line adapter:
Both lines matter. Without c.Request = r, any context the middleware added is lost downstream. Without c.Abort(), a denied request still reaches your handler, which writes a 200 body over the 403 — a fail-open bug. gin/main_test.go pins both behaviours.

Why Fiber differs

*fiber.App is not an http.Handler, so the chain is mounted as Fiber middleware and the login endpoint is mounted by hand. Reading the principal still works: the adaptor copies the request-context values into fasthttp user values, and *fasthttp.RequestCtx — what c.Context() returns — is itself a context.Context whose Value reads them back.

Trying it out

Same transcript against every one of the five:
Log in as bob/hunter2 instead and /admin/panel returns 403 — the framework route group rejects a caller without ROLE_ADMIN. Posting to /login with no X-CSRF-Token returns 403 on all five, and every response carries the default security headers.

Role hierarchy caveat

middleware.RequireRole is a literal authority check — it consults no role hierarchy. Under ADMIN > STAFF > USER, an ADMIN caller is denied by RequireRole("STAFF"). For a hierarchy-aware per-route guard, use a scoped rule set (also middleware, so it drops into the same adapters):
Details and the chain-level alternative: Web Frameworks → Role hierarchy in per-route checks.

What these examples prove

  • One securityhttp.NewHandler call secures Gin, Echo, chi, and gorilla/mux identically — no framework-specific package, no adapter
  • Framework-native route groups compose with go-security’s guards in every framework, including Fiber
  • The authentication is reachable from every framework’s handler, because it travels on the request context
  • The cookies here are Secure: false so the examples work over plain HTTP on localhost. In production keep the secure defaults and serve TLS.