Stage 7 · Master
Authentication and Authorization
Enforcing RBAC in Middleware
How authentication claims become request-scoped actor context, why permission checks belong at the HTTP edge, and how Meridian should enforce tenant-scoped RBAC with net/http middleware rather than pretending Gin middleware already exists.
Why Edge Enforcement Matters
Authorization checks should be attached as early and as consistently as possible in the request lifecycle. If every handler performs its own ad hoc permission logic, some will forget, some will check the wrong role, and some will deny correctly but after doing unnecessary work. Middleware exists to make the secure path the boring path: authenticate once, load actor context once, check declared permissions before the handler touches business logic.
This does not mean all authorization logic belongs in middleware. Middleware is the right place for generic edge checks such as 'does the token contain billing:invoice:read?' It is not the right place for domain invariants such as 'may a paid invoice be reopened?' RBAC says who may attempt a class of action. Domain rules still decide whether the attempted operation is valid in the current state.
Actor Context
A request should not repeatedly parse the Authorization header or repeatedly reconstruct the caller's permissions. After token verification succeeds, the middleware stack should store a typed actor object in request context. Later middleware and handlers then read one stable representation of the caller rather than reparsing headers and JSON claims independently.
Permission Middleware
In net/http, middleware is simply a function that wraps an http.Handler. That is enough for RBAC. An authentication wrapper verifies the token and stores the actor. A permission wrapper checks for required permission strings and rejects the request with the standard failure envelope if any are missing. The route table then becomes a readable authorization inventory: every protected route declares what it requires right where it is registered.
Tenant Scope and RBAC
Permission strings alone are insufficient in a multi-tenant system. An actor may hold billing:invoice:read inside tenant A and still be forbidden from reading tenant B's invoices. That means the middleware stack must enforce both dimensions: the caller possesses the permission and the request is operating inside the same trusted tenant scope. This is where JWT claims, gateway tenant resolution, and libs/platform/tenancy come together.
This ordering is especially important for Meridian because the gateway will eventually resolve a human-friendly slug into a tenant UUID and forward that UUID via X-Tenant-Id. Internal services should trust that tenant context, compare it against actor claims where necessary, and refuse mismatches early. The handler should receive a request that is already authenticated, tenant-scoped, and permission-checked.
Middleware is for edge concerns: token verification, actor loading, tenant binding, and permission checks. Once it starts loading half the domain graph and enforcing business workflow rules, observability and maintainability both suffer.
Current Repository Status
apps/gateway/internal/middleware/middleware.go currently implements only panic recovery. No auth middleware, actor context, or RBAC enforcement exists in meridian yet. This chapter specifies the net/http middleware shape the implementation phase should adopt.
- Authenticate once, construct one typed actor, and store it in request context.
- Attach permission checks in middleware so protected routes declare their requirements explicitly.
- Enforce tenant scope alongside permission scope in multi-tenant requests.
- Keep RBAC checks separate from deeper domain workflow rules.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.