Layers route-rule authorization on top of the Basic Auth example: the route-rules DSL, a role hierarchy, the default-deny posture, and 401-vs-403 exception translation, all composed into one net/http middleware chain.

The integration story, end to end

  1. Build a UserService + password Encoder + authn.Manager (same as the basic example).
  2. Define a role hierarchy: ADMIN > STAFF > USER (a superior role implies the subordinate ones).
  3. Declare first-match-wins route rules with authz.Routes():
    • /admin/** requires ROLE_ADMIN
    • /api/** requires any authenticated user
    • /public/** is open to everyone (PermitAll)
    • AnyRequest is denied (DenyAll) — anything not matched above is rejected, and exception translation turns that denial into 401 (anonymous) or 403 (authenticated-but-unauthorized).
  4. Compose the chain: SecurityHeaders -> BasicAuth -> authz route middleware, then wrap the application routes.

Full source

Trying it out

Admin implies staff implies user, so admin reaches everything:
/secret has no registered route at all — the authz middleware denies it before the request ever reaches the mux, based purely on the default-deny AnyRequest().DenyAll() rule.

What you get for free

  • Default-deny: Routes() fails at startup unless a terminal catch-all rule (AnyRequest) is declared, so an unmatched path can’t silently fall through open
  • First-match-wins route rules, evaluated in declaration order
  • A role hierarchy so HasRole("USER") is satisfied by ADMIN or STAFF callers too
  • 401-vs-403 exception translation: anonymous denials become 401, an authenticated-but-insufficiently-privileged denial becomes 403
  • The same rule engine (authz.Guard) usable at the service layer, the @PreAuthorize equivalent