Middleware Reference
ezauth provides several middlewares to handle authentication and authorization.
Core Middleware
LoginRequiredMiddleware
Checks if a user is authenticated. This middleware is "content-aware":
- Browser Request: Redirects to the configured Login Page.
- API Request (
/api/*orAccept: application/json): Returns401 Unauthorized.
func (h *Handler) LoginRequiredMiddleware(next http.Handler) http.Handler
Usage:
r.Group(func(r chi.Router) {
r.Use(auth.LoginRequiredMiddleware)
r.Get("/dashboard", dashboardHandler)
})
SessionMiddleware
Manages the cookie-based session (via scs) and loads the authenticated user into the request context for Form-based (non-API) routes. Must be mounted on the router for GetSessionUser, GetSessionTokens, IsAuthenticated, and flash-message helpers to work.
func (h *Handler) SessionMiddleware(next http.Handler) http.Handler
Usage:
r.Use(auth.SessionMiddleware)
LoadUserMiddleware
Loads the authenticated user into the request context from a Bearer token or API key, without requiring the cookie-based session. This allows downstream handlers to use auth.GetSessionUser(ctx) without needing access to the Handler instance. This is useful if you want to use ezauth's user data in your own handlers that are not part of the auth package logic.
func (h *Handler) LoadUserMiddleware(next http.Handler) http.Handler
JSON API Middleware
AuthMiddleware (Bearer)
Validates the Authorization: Bearer <token> header. It parses the JWT, verifies the signature against the configured signing key (EZAUTH_JWT_SECRET for the default HS256 mode, or the asymmetric key(s) under EZAUTH_JWT_* — see Asymmetric JWT Signing (JWKS)), re-checks the token's subject is still an active user, and sets the user ID in the context. When EZAUTH_JWT_ISSUER/EZAUTH_JWT_AUDIENCE are configured, the token's iss/aud claims must also match.
func (h *Handler) AuthMiddleware(next http.Handler) http.Handler
APIKeyMiddleware
Validates the X-API-Key header. It checks against the configured Master API Key (and, during a rotation, Cfg.PreviousApiKey too — see Scoped API Keys) or looks up an API Key token in the database.
func (h *Handler) APIKeyMiddleware(next http.Handler) http.Handler
RequireAPIKeyScope
Requires the API key used to authenticate the request (via APIKeyMiddleware, which must run upstream) to include the given scope. An unscoped key — including the master config API key, which has no associated Token — has full access. See Scoped API Keys.
func (h *Handler) RequireAPIKeyScope(scope string) func(http.Handler) http.Handler
Authorization Middleware (RBAC)
See Roles & Permissions (RBAC) for the full picture — these check the real RBAC tables, not the legacy User.Roles string field.
RequireRole
Requires the authenticated user (identified via the request context set by AuthMiddleware or LoadUserMiddleware/SessionMiddleware — must run downstream of one of those) to hold the given role. Returns 401 if no user is in context, 403 if they lack the role.
func (h *Handler) RequireRole(role string) func(http.Handler) http.Handler
RequirePermission
Same as RequireRole, but checks a permission, resolved transitively through every role granted to the user.
func (h *Handler) RequirePermission(permission string) func(http.Handler) http.Handler
RequireOrgMembership / RequireOrgRole
Scope a route to members of the "current organization" (set by an OrgLoaderMiddleware mounted upstream — see Organizations) instead of relying solely on a blanket, application-wide admin gate. ezauth's own org service methods (OrganizationGetByID, OrgMemberAdd, etc.) perform no membership check themselves — matching every other RBAC-gated method in this package, which rely entirely on the HTTP-gate layer for authorization — so mount one of these yourself on organization routes if you've customized WithAdminAuthz to something other than the default global-admin-only gate, and want per-organization scoping instead of (or in addition to) it. RequireOrgMembership accepts any role; RequireOrgRole additionally requires an exact role match. Both return 401 if no user is in context, 403 if no org is in context, the caller isn't a member, or (for RequireOrgRole) their role doesn't match.
func (h *Handler) RequireOrgMembership(next http.Handler) http.Handler
func (h *Handler) RequireOrgRole(role string) func(http.Handler) http.Handler
Organization Middleware
OrgLoaderMiddleware
Resolves the "current organization" for a request via an app-supplied OrgLoader (ezauth doesn't presume how an org is identified — URL param, subdomain, header, etc.) and loads it into context. Mirrors LoadUserMiddleware exactly. See Organizations.
type OrgLoader func(context.Context) (*models.Organization, error)
func (h *Handler) OrgLoaderMiddleware(loader ezmiddleware.OrgLoader) func(http.Handler) http.Handler
Standalone Middleware Package
Every middleware above is a thin (h *Handler) wrapper around a standalone
function in github.com/josuebrunel/ezauth/pkg/handler/middleware (imported
as ezmiddleware elsewhere in this reference). Reach for the package
directly if you're composing routes without a Handler instance — e.g.
protecting a non-ezauth route with just a TokenGetter/RoleChecker
implementation.
// Same logic as the Handler methods above, taking explicit dependencies
// (a RoleChecker, TokenGetter, etc.) instead of a *Handler.
func AuthMiddleware(keyFunc jwt.Keyfunc, validMethods []string, userRepo UserActiveGetter, extraOpts ...jwt.ParserOption) func(http.Handler) http.Handler
func APIKeyMiddleware(configApiKey, previousApiKey string, tokenRepo TokenGetter, userRepo UserActiveGetter) func(http.Handler) http.Handler
func RequireAPIKeyScope(scope string) func(http.Handler) http.Handler
func RequireRole(checker RoleChecker, role string) func(http.Handler) http.Handler
func RequirePermission(checker PermissionChecker, permission string) func(http.Handler) http.Handler
func RequireOrgMembership(checker OrgMembershipChecker) func(http.Handler) http.Handler
func RequireOrgRole(checker OrgMembershipChecker, role string) func(http.Handler) http.Handler
func LoginRequiredMiddleware(authChecker AuthChecker, loginPath string) func(http.Handler) http.Handler
func LoadUserMiddleware(loader UserLoader) func(http.Handler) http.Handler
func OrgLoaderMiddleware(loader OrgLoader) func(http.Handler) http.Handler
func LoadAndSaveMiddleware(sm *scs.SessionManager) func(http.Handler) http.Handler
func SessionMiddleware(sm *scs.SessionManager, loader UserLoader) func(http.Handler) http.Handler
// Chains middlewares in order: Chain(a, b)(h) runs a, then b, then h.
func Chain(middlewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler
Context-key constants — set by the middleware above, read via
context.Value: UserContextKey, UserObjectContextKey,
ImpersonatorContextKey, SessionTokensContextKey,
SessionImpersonatorKey, APIKeyScopesContextKey, OrgContextKey,
OrgObjectContextKey.
Rate limiting — the EZAUTH_RATE_LIMIT_* settings (see
Configuration) configure two
RateLimiter instances ezauth mounts internally from this same config
struct: a general one at the router root, and a stricter one applied only to
login/password-reset/passwordless/SMS-OTP/MFA-verification routes (Form and
JSON API versions of each share one budget). You can also run RateLimiter
standalone:
type RateLimitConfig struct {
Enabled bool
Requests int
Window time.Duration
ByClientIP bool
}
func NewRateLimiter(cfg RateLimitConfig) *RateLimiter
func (rl *RateLimiter) Middleware(next http.Handler) http.Handler