go-security is net/http middleware: every leg of the chain is a
func(http.Handler) http.Handler, and securityhttp.NewHandler returns a single
http.Handler. Any Go router that implements http.Handler — which is nearly
all of them — therefore integrates in one line, with no adapter and no
framework-specific package to install.
Support matrix
Pattern 1 — wrap the router (recommended)
The security chain sits outside the framework’s router, so it runs before
routing happens. That is what you want: security headers, CORS, rate limiting,
session lookup, CSRF, and default-deny authorization all apply to every request,
including the ones your router has no route for. It is also the only way to get
the login endpoint securityhttp.WithLogin mounts.
Then serve it like any handler:
Route patterns in authz.Match are matched against the raw request path, before
your framework parses path parameters. Write them as paths (/admin/**,
/api/v1/**), not as framework route templates (/users/:id, /users/{id}) —
those template strings never appear in a request URL.
Pattern 2 — middleware inside the framework
Chain-level authz.Match rules cover most needs, but sometimes you want the
guard next to the route, in the framework’s own idiom (r.Group, e.Group,
r.Route). Every guard in web/middleware — RequireAuthenticated,
RequireRole, RequireAuthority, BasicAuth — is a plain net/http middleware,
so it composes with whatever adapter the framework offers.
Use both layers together: keep the chain outside (headers, session, CSRF,
default-deny) and add per-route role checks inside.
chi and gorilla/mux
Both are net/http-native, so the middleware is used as-is:
Echo
Echo ships the adapter:
echo.WrapMiddleware puts the request the middleware passed down back on the
Echo context, so handlers downstream see the authentication.
Gin
Gin has gin.WrapH for handlers but no wrapper for middleware, so write the
adapter once:
Both details matter. Skip c.Request = r and any context the middleware added is
lost downstream. Skip c.Abort() and a denied request still reaches your
handler, which then writes a 200 body after the 401 — a fail-open bug.
Role hierarchy in per-route checks
middleware.RequireRole / RequireAuthority are literal authority checks —
they compare what the principal holds against what you asked for, and consult no
role hierarchy. Under ADMIN > STAFF > USER, an
ADMIN caller is denied by middleware.RequireRole("STAFF").
Chain-level rules are hierarchy-aware, because WithHierarchy is a Routes()
option:
If you want the hierarchy-aware check next to the route instead, build a scoped
single-rule rule set — authz.Routes returns ordinary middleware, so it goes
through the same adapters as everything above (AnyRequest here means “any
request that reaches this group”):
authz.Guard is the third option, but it is not middleware — it returns
func(ctx, target) error for service-layer calls, where the decision depends on
the domain object rather than the path.
Reading the principal
The authentication lives on the request context, so it is always
core.MustAuthentication(ctx) — only the way to reach ctx differs:
Fiber
Fiber runs on fasthttp, not net/http, so *fiber.App is not an
http.Handler and securityhttp.NewHandler cannot wrap it. Use
securityhttp.New to get the chain and mount it with Fiber’s adaptor —
middleware.Chain.Then already has the func(http.Handler) http.Handler shape
adaptor.HTTPMiddleware expects:
core.MustAuthentication(c.Context()) works because 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.
Every other fasthttp-based framework (Atreugo, fasthttp-routing) has the same
constraint and the same fix: bridge with an adaptor, and remember that any
security middleware you skip on that path is simply not running.
Runnable examples
The repository ships one small program per framework — same routes, same
security options, only the framework wiring differs — in a separate Go module so
the library itself keeps its standard-library-plus-x/crypto dependency
guarantee:
→ Framework integration walkthrough
Each serves GET / (any authenticated user), GET /admin/panel (ADMIN, enforced
by the framework’s own route group), GET /csrf, and the POST /login endpoint:
The examples set Secure: false on their cookies so they work over plain HTTP on
localhost. In production keep the secure defaults and serve TLS.