Server-Side Enforcement
Server-Side Enforcement
The authoritative RBAC code lives in packages/api/internal/middleware/rbac.go. Every protected route is wrapped with middleware.RequireRoles(roles...) in packages/api/internal/router/router.go.
13.4.1 Middleware contract
Three things to know:
adminis hard-wired as a superset role at the middleware layer — it bypasses every check.apikeyanddidauthschemes pass through. This is intentional — both schemes already proved an identity (a long-lived secret or a fresh DID signature). The handler is then responsible for ownership / resource-scope checks.- Empty role yields
401, wrong role yields403. Clients should distinguish: 401 = “log in”; 403 = “this account can’t do that”.
13.4.2 Wiring in the router
13.4.3 Owner-only enforcement inside handlers
Some endpoints (e.g. PUT /dids/{did}) accept any authenticated caller at the router layer but enforce resource ownership inside the handler. The handler typically:
Best practice for new endpoints: if the resource has an owner column, always perform the owner check after RBAC, never before. RBAC restricts the role; ownership restricts the instance.
13.4.4 Error response shape
Failed RBAC checks return the canonical error envelope (verified in rbac.go:73-81):
A failed auth (no header, malformed scheme, expired JWT) instead returns:
Why two shapes? RBAC errors are produced by
rbac.go:writeRBACErrorand follow the new{ success, error: { code, message } }envelope. Auth errors are produced byauth.go:writeAuthErrorand use the legacy{ error, status }envelope. SDK clients should accept both shapes; new endpoints should standardise on the RBAC shape.