Stage 7 · Master
Authentication and Authorization
Permission Modeling with RBAC
Why role names alone stop scaling, how service:resource:action permission strings create a stable authorization vocabulary, and how tenant-scoped memberships in Meridian should expand into reviewed permission bundles.
Why Roles Need Vocabulary
Role names are convenient for humans and inadequate for precise systems. 'Admin' sounds informative until the codebase contains multiple services, nested scopes, and a growing set of operations. Admin of what? Tenant configuration, resident data, announcement publishing, invoice generation, payment reconciliation? Once those distinctions matter, route and service code need a more precise language than role folklore.
RBAC works best when roles are treated as assignment conveniences and permissions are treated as the actual enforcement vocabulary. Humans grant a role because it is manageable. Middleware and services check a permission because it is precise. That separation keeps the API honest and makes reviews possible. A reviewer should be able to see one string such as billing:invoice:read and know exactly which power is being granted.
Permission Strings First
Meridian's stated model is service:resource:action permission strings layered over tenant-scoped memberships. This is a strong pattern because it encodes ownership and intent directly into the permission itself. The service segment names the bounded context. The resource segment names the thing being acted on. The action segment names the power. That gives the string explanatory value before any code comments are read.
| Pattern | Why teams choose it | Why Meridian should prefer or reject it |
|---|---|---|
| Check role names directly in handlers | Short code at first | Rejected because meaning drifts and route reviews become guesswork |
| Boolean permission columns on memberships | Easy SQL for a tiny product | Rejected because every new action widens schema and still lacks vocabulary |
| Permission strings grouped into roles | Requires initial design work | Chosen because the same vocabulary works for tokens, middleware, and audits |
Roles as Reviewed Bundles
Roles still matter because no customer administrator wants to assign twelve individual permissions by hand for every membership. The trick is to keep roles boring. A role is a named bundle of reviewed permissions. It is not a magical parallel authorization system. The enforcement layer should not care whether a permission came from tenant_admin or billing_manager. It should care that the concrete permission is present.
Wildcards look tempting at this point. A string like billing:*:* seems like an easy way to create a super-role. The long-term cost is that audits, code review, and principle-of-least-privilege all become much harder. Explicit permission bundles are more work up front and far easier to trust later.
Meridian-Specific Examples
Meridian's bounded contexts give good examples of why permission modeling must be richer than CRUD slogans. community:announcement:write is meaningfully different from community:complaint:resolve. billing:invoice:write is different from billing:payment:record because invoice generation and payment capture often have different operational risk. identity:membership:write is different again because it changes who may act in the tenant at all.
{
"sub": "user_123",
"tenant_id": "tenant_456",
"roles": ["billing_manager"],
"permissions": [
"billing:invoice:read",
"billing:invoice:write",
"billing:payment:record"
]
}The token carries the concrete permission list so request-time authorization can stay local and fast. Short token lifetimes keep that snapshot acceptably fresh.
This model also teaches a deeper architectural habit: permission namespaces should align with service ownership. When a new service appears later, it should create its own permission namespace rather than stuffing new actions into old strings that no longer describe reality. Authorization vocabulary is part of service design, not a side table appended afterward.
Current Repository Status
apps/identity-service/internal/domain/domain.go is still empty, and no permission catalog or JWT claim expansion exists in meridian today. This chapter specifies the authorization vocabulary the repository is clearly intended to grow into.
- Treat permissions as the enforcement vocabulary and roles as human-manageable bundles.
- Name permissions as service:resource:action so they explain themselves.
- Keep permission bundles explicit and reviewable; avoid wildcards and vague role folklore.
- Align permission namespaces with service boundaries so the model scales as the platform grows.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.