Middleware
Global middlewares (applied to every route)
Registered in bootstrap — already enabled by default:
| Middleware | Description |
|---|---|
Recovery() |
Catches panics — shows the debug page (dev) or 500 (prod) |
MaxBodySize(32MB) |
Limits body size — defense against giant payloads |
SecureHeaders |
HSTS, X-Frame-Options, CSP (configurable — see below) — production only (APP_ENV=development doesn't enable it) |
AllowedHosts() |
Blocks unauthorized hosts (ignored in dev) |
csrf.Middleware |
Validates the CSRF token on unsafe methods |
Optional middlewares
| Middleware | Usage | Description |
|---|---|---|
Compress |
r.Use(compress.Compress) |
Gzip compression of responses |
CORS(origins) |
r.Use(secmiddleware.CORS(...)) |
CORS headers for allowed origins |
RequireAuth(a) |
per route or global | Requires a Bearer JWT token — stateless APIs |
RequireLogin(store, url) |
per route or global | Requires an active session — SSR views; redirects to url if not authenticated |
RateLimit(max, window) |
per route (recommended on login) | Maximum of max requests per IP per window; excess requests get a 429 |
LocalhostOnly |
per route (e.g. internal dashboards) | 403 for any IP that isn't loopback — this is what protects /kyrux/debug/ |
MaxBodySize(n) |
per route, in addition to the global 32MB | A smaller (or larger) limit specific to a route (e.g. upload) |
import (
"kyrux/core/compress"
secmiddleware "kyrux/core/security/middleware"
)
// Gzip compression
r.Use(compress.Compress)
// CORS
r.Use(secmiddleware.CORS([]string{"https://mysite.com"}))
// Require an active session on SSR views — redirects to /login/ if not authenticated
r.Use(secmiddleware.RequireLogin(fw.Sessions, "/login/"))
// Require JWT on API routes
r.Use(secmiddleware.RequireAuth(fw.Auth))
// Rate limit per route — essential on login (Argon2 is expensive by design):
router.Path("POST", "/login/",
secmiddleware.RateLimit(10, time.Minute)(views.LoginPost(fw)), "login_post"),
Custom middleware
func LogMiddleware(next router.HandlerFunc) router.HandlerFunc {
return func(ctx *router.Context) {
log.Printf("%s %s", ctx.Request.Method, ctx.Request.URL.Path)
next(ctx)
}
}
// Apply globally
r.Use(LogMiddleware)
Per-route middleware (via wrapper)
func Register(r *router.Router) {
// Session-protected SSR route
loginRequired := secmiddleware.RequireLogin(fw.Sessions, "/login/")
r.Handle("GET /dashboard/", loginRequired(func(ctx *router.Context) {
render.For("painel").Render(ctx, "dashboard.html", nil)
}))
// JWT-protected API route
jwtRequired := secmiddleware.RequireAuth(fw.Auth)
r.Handle("GET /api/perfil/", jwtRequired(func(ctx *router.Context) {
claims := ctx.Get("claims").(*auth.Claims)
ctx.JSON(200, map[string]string{"user_id": claims.UserID})
}))
}
Configurable Content-Security-Policy (CSP)
The CSP sent by SecureHeaders in production has a strict default
(secmiddleware.DefaultCSP: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:) — configurable at
two levels, without touching core/:
Global — swaps the policy for the whole site, normally from
CSP_POLICY in .env (bootstrap already calls this for you):
secmiddleware.SetCSP(cfg.Security.CSPPolicy) // an empty policy keeps DefaultCSP
Per route — overrides just one page that needs a one-off exception (a third-party script/iframe only there — e.g. an embedded map or a captcha provider), without loosening the rest of the site's policy:
mapCSP := "default-src 'self'; frame-src https://www.google.com/maps/"
router.Path("GET", "/contact/",
secmiddleware.CSPOverride(mapCSP)(views.ContactView(fw)), "contact"),
This works because per-route middleware runs after the global one
(SecureHeaders included) in the chain — the Set in CSPOverride
replaces, never adds to, the header SecureHeaders already wrote. It also
works with SecureHeaders off (development): it just adds one extra
header, harmless.