Build1 publisher3 min readPublished
A permissions playbook makes every access check answer the same four-part question
A dev.to playbook argues authorisation is a domain capability enforced server-side over principal, action, resource and context. Its test for reaching for a policy engine is whether rules must be shared, audited or versioned apart from individual endpoints.
The Engineer · Build desk

What happened
- A dev.to playbook argues authorisation is a domain capability instead of scattered endpoint conditionals, with the decision enforced server-side before any data or side effect is exposed.
- It lists six conditions that make a single decision surface necessary, including multi-role users, tenancy boundaries, delegated access, several services sharing one rule, and audit needs.
- The default is no access, with narrow reviewable grants added where a business rule justifies them and explicit prohibitions kept for suspended accounts, cross-tenant access and MFA-gated actions.
- It states that an authenticated user is not automatically authorised, that client-side checks never replace server-side authorisation, and that a role label alone is not a complete permission model.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Once actions are named as business operations, the route table no longer works as the inventory of protected operations, and the domain model has to supply one.
- exposure A grant keyed to an email address or a project slug follows the string, so renaming either can move access along with it.
- cost The negative rows are test surface somebody has to write and maintain; delegated-access expiry and missing step-up authentication do not show up in happy-path suites.
Start at the call site. The playbook separates permission, which it defines as a grant or entitlement, from authorisation, the request-time decision and server-side enforcement that applies those grants to a principal, action, resource and context [2]. Every protected operation reduces to one question: "Can principal P perform action A on resource R in context C?" [9] Actions are named as business operations such as invoice.approve or document.download, not HTTP verbs or UI labels [9]. Create and list operations have no row to point at, so the resource becomes the destination container or the tenant [9]. In my view most codebases break that last rule: a list handler that takes no resource argument has nothing to deny, so it filters rows after the query has already run.
The layering underneath is authentication, authorisation, entitlements and relationships, and enforcement [6].
Two checks that endpoint code tends to merge are kept apart. "Can Alice approve invoice #123?" is a permission question; "Can an approved invoice still be edited?" is a business rule [8]. The playbook says an authorised user can still fail business validation, and a valid workflow transition can still be forbidden for that user [8]. Workflow state may be fed into the authorisation decision deliberately, but permissions must not substitute for state-transition validation or domain invariants [8].
Then the engine question. The playbook's criterion, verbatim: "A few stable rules may be clearer as centrally tested application code; a policy engine becomes valuable when rules are expressive, auditable, shared across services, or need to evolve independently of individual endpoints" [3]. It puts a guardrail on the other side too: "Do not introduce a policy engine merely to avoid a short, clear rule" [5]. So there are two things to count before deciding: how many deploy units must agree on the same rule, and whether anyone needs that rule's version history separately from the service's. With one service and no audit requirement, tested application code gives you the same tuple and the same enforcement point. The playbook does not name a policy engine or give a cost for operating one, so the build-or-adopt call cannot be settled on numbers it supplies.
Identifier choice is where the model tends to break. Policy-facing data should use opaque, immutable IDs, and the playbook calls display names, email addresses and mutable slugs poor policy identifiers [13]. In a multi-tenant system the tenant or organisation is a first-class container, each resource has a clear containment path, and a principal's tenant membership comes from a trusted source instead of a tenant ID the browser supplied [14].
The adoption cost sits in the matrix and the enforcement points. Every server-side path that reads protected data, changes state, invokes a privileged integration or produces a sensitive export needs an enforcement point, and row-level security, query predicates and storage access controls stay in place as defence in depth [11]. The matrix is built from the domain, not from framework routes, and five categories of negative case are written deliberately: cross-tenant access, inactive users, deleted or locked resources, delegated access expiry, and privileged actions without step-up authentication [12][2]. The document labels itself a point-in-time version of a playbook its author says will be kept up to date [15].
What to watch
- A later revision that names an actual policy engine, or gives the cost of running one, would make the build-or-adopt test decidable on figures instead of criteria.
- A published authorisation matrix template, negative rows included, would show how much test surface the approach adds to an existing service.