Build a web app permission matrix before building screens
A web-app permission matrix turns “staff can use the system” into decisions about a particular person, action and record. Write the rules before development so the team can see where an organisation boundary, assignment or record state changes the answer. The worksheet below gives each decision an owner and a test case, including requests that must be denied.
Define the decision the app must make
Start with a sentence such as “An assigned technician may update the work status of an open job in their organisation.” It identifies the subject, action, record and conditions. “Technicians can edit jobs” hides the limits that matter. A person may be signed in and still have no right to another organisation’s job, an unassigned job or a closed job.
List the records that need protection, including attached files, notes, lists and exports. List actions separately: viewing a list, opening one record, creating, changing, assigning, approving, downloading and exporting are different decisions. OWASP recommends enumerating user types, resources and operations during design, granting only necessary privileges, denying unspecified access and checking permission on every request. The matrix is the business rule to implement and test; a hidden control or an unguessable record address cannot enforce it.
Copy this permission-matrix worksheet
Create one row for each materially different decision. Copy the column order below into a shared sheet or document. Use synthetic record identifiers in public planning material and keep real account, customer and case data in an access-controlled workspace.
- Rule ID | Subject or role | Record type and scope | Action | Required relationship, attributes and state | Decision | Reason and business owner | Enforcement surface | Allowed and denied test IDs.
- Subject or role: name the actual job, service account or external user. Record how that role is granted and removed in the private operating plan.
- Record type and scope: distinguish one record, a collection, an attachment and an export; state the organisation, team or ownership boundary.
- Action: use a verb that can be checked, such as read, create, change status, assign, download, export or delete. Split actions with different rules into separate rows.
- Conditions and decision: write “allow only if…” with the facts checked at the time of the action, or write “deny”. Missing or unknown conditions mean deny until reviewed.
- Reason and owner: explain the work need, name the person accountable for approving the rule and record any approved exception with an expiry.
- Enforcement and tests: identify every route, API, background job and file endpoint that performs the action; give the allowed case and at least one denied case stable test IDs.
Add record relationships and changing conditions
A role answers only part of the question. The app may also need to know whether a job belongs to the user’s organisation, whether the person is assigned to it, whether the record is still open or whether a temporary delegation remains valid. NIST describes attribute-based access control as evaluating characteristics of the subject, object and operation, sometimes with environment conditions. OWASP also recommends relationship-based rules where a role alone cannot express who may access a particular object. Pick the simplest rule that states the real boundary, then document where each required fact comes from.
Treat a list or export as a set of protected records, not as a shortcut around single-record checks. A user allowed to open one assigned job does not automatically get every job in a search result or a bulk download. Decide whether the action is denied altogether or the returned set is restricted to records the user may see. Apply the same rule to direct links, APIs and attachment downloads. Recheck the relationship when the action occurs; assignment and membership may have changed since the page loaded.
Hypothetical example: an internal repair-job app
Imagine an equipment-repair business planning an internal job app. The roles below, their decisions and the record types are invented solely to show how to fill in the worksheet; they are not Kinetivy client work or a recommended policy for every repair business. A job belongs to one organisation, may be assigned to a technician, and can be open or closed. A separate cost record holds commercial details.
Read each line in the same column order as the worksheet. “Allow” always means allow only when the stated conditions are true. All other combinations are denied by default until the business owner approves a rule.
- R1 | Technician | Single job in own organisation | Read | Assigned to technician; account active | Allow | Complete assigned work; service manager | Job view and job API | A1: assigned job; D1: another technician’s job; D2: another organisation’s job; D3: revoked account.
- R2 | Technician | Single job in own organisation | Change work status | Assigned; job open; permitted transition | Allow | Report progress; service manager | Status API and any retry path | A2: assigned open job; D4: closed job; D5: assignment removed after page load.
- R3 | Dispatcher | Jobs in own organisation | Assign technician | Job open; technician belongs to same organisation | Allow | Allocate work; operations owner | Assignment API and bulk action | A3: valid assignment; D6: cross-organisation assignee; D7: closed job.
- R4 | Finance clerk | Approved cost record in own organisation | Read | Cost approved; no access to private job notes | Allow | Prepare invoice; finance owner | Cost view and cost API | A4: approved cost; D8: draft cost.
- R5 | Finance clerk | Approved cost collection in own organisation | Export | Every included cost approved; internal notes excluded; account active | Allow | Prepare invoice batch; finance owner | Export endpoint and background job | A5: filtered approved costs; D9: draft cost included; D10: internal note included.
- R6 | Technician | Cost record | Read | No approved business need | Deny | Limit commercial access; finance owner | Cost view and API | D11: direct cost URL.
- R7 | Technician | All-job collection | Export | No approved business need | Deny | Limit bulk access; service manager | Export endpoint and background job | D12: copied export request.
Give every boundary an allowed and denied example
For each allow row, test the positive case and change one fact at a time: another organisation, a different owner, a closed state, an expired delegation or a revoked account. Try the same operation through the visible interface and a direct request with a copied record identifier. OWASP’s API security guidance says every function that uses a client-supplied identifier to access a record needs an object-level authorization check. OWASP’s authorization-testing guidance treats role, feature and sometimes business data as matrix dimensions that can drive repeatable tests.
Include collection searches, exports, file downloads and background actions. Check that a denied change did not alter the record and that a denied list did not leak titles or counts the user must not know. Record the expected result and a safe error response without exposing private details. The web-app acceptance-testing guide explains how to run release journeys and keep evidence; this matrix defines the permission decisions those tests should prove.
Approve the rules before build and revisit them after change
Have the people who own the business process review every allow rule and the denial cases. Resolve vague words such as “manager”, “own” and “active” by identifying the authoritative record and the event that changes it. Give technical reviewers the matrix before route and database design, so each protected surface has a defined enforcement point. Where a rule depends on an external system or cached assignment, agree what happens when that fact is unavailable or stale; do not turn an unknown answer into access.
Keep the matrix with versioned requirements. Review it when a new role, record type, bulk feature, partner organisation or status transition is added, and when people leave or change responsibilities. A permission matrix does not choose login and account-recovery methods, file-validation rules or the contents and retention of an audit log. Those are separate requirements, although each must respect the access decisions recorded here.
Questions worth asking.
Is a list of roles enough for a small web app?
Only if every person in a role may perform the same action on every record of that type. Most shared apps also need an organisation, ownership, assignment or record-state condition. Write those conditions beside the role so “staff” does not accidentally mean “all staff, all records”.
Does hiding a button protect a record?
No. A hidden button can make an interface clearer, but the server must check permission for the requested action and specific record. Test a direct request and a copied record address under an account that should be denied.
Should administrators automatically see and change everything?
No automatic blanket grant is needed. List the administrator’s actual account and support tasks as separate actions, then decide whether any business records are necessary for those tasks. Give an exceptional access path an owner, reason and review rule.
When should the permission matrix be updated?
Update it before adding a role, record type, new action, export or partner boundary, and whenever the rule behind an existing permission changes. Turn the changed allow and deny cases into tests before accepting that release.
Sources & further reading
- OWASP: Authorization Cheat Sheet ↗︎
- NIST SP 800-162: Attribute Based Access Control ↗︎
- OWASP API Security Top 10: Broken Object Level Authorization ↗︎
- OWASP: Authorization Testing Automation Cheat Sheet ↗︎
Examples in this guide are illustrative. Read about our editorial approach.