How to set access roles for a small business app
Write down every action people take in the app, then group the actions into a few roles based on what each person is responsible for. Give each role only the actions it needs and deny everything else by default. Treat exporting data, deleting records and changing roles as sensitive actions with a named owner. Before launch, try each action as each role and confirm the results match your table.
Start with actions, not job titles
Job titles hide detail. Two people called manager may need different things, and a part-time helper may need one task only. List the actions instead, using plain verbs: view a customer, edit an order, approve a refund, export a report, delete a record, invite a colleague.
Then note which records each action touches. Seeing a customer's name is not the same as seeing their payment history, and the table should show that difference.
A role-planning checklist
Use one row per action and one column per role. Mark each cell as allowed, not allowed or allowed for own records only. Add these columns so the table stays useful after launch:
- Action in a plain verb phrase
- Records it touches
- Roles that may do it
- Whether it is limited to the person's own records
- Whether it needs a second person to approve
- Who owns the decision to change this rule
Keep the first version small
A reasonable starting point for a small app is three or four roles, for example someone who can only view, someone who can add and edit, someone who can approve, and an administrator. Treat this as a suggestion to adjust, not a rule. More roles mean more rules to explain, review and test. Add a role only when a real task cannot be covered by the existing ones.
Avoid one role per person. If a rule exists for a single individual, ask whether it is really a task that should be shared.
Deny by default and give the least needed
The OWASP Authorization Cheat Sheet lists enforcing least privileges and denying by default among its recommendations. It explains that mistakes in access control may happen, especially when requirements are complex, so an application should not rely only on explicit rules matching every possible request. In practice that means a new action or a new screen is closed to everyone until you decide who may use it.
Check permissions where the work happens, on the server, not only by hiding a button. A hidden button is a convenience for the user. It does not stop someone who sends the request directly.
Treat sensitive actions separately
Some actions deserve extra care because they are hard to undo or expose a lot at once. Decide an owner for each of them before launch.
- Exporting all records or a full customer list
- Deleting or merging records
- Changing someone's role or inviting a new user
- Seeing personal details of people outside the user's own work
- Changing prices, rates or approval limits
An illustrative example
Imagine a small equipment hire company with an app for bookings. A counter assistant can view equipment and create bookings. A depot lead can also mark items as returned or damaged. The owner can approve discounts, export the monthly booking list and invite staff. A delivery driver can only see the day's drop-off list and mark each one delivered. None of these people can change roles except the owner. This example is hypothetical.
Own records only: a two-user check
When a cell in your table says allowed for own records only, the app has to confirm on every request for a specific record that the record belongs to the person asking. OWASP describes an insecure direct object reference as having three ingredients: an object such as an account or document, a reference to it such as an ID or slug, and a missing object level authorization check. Leaving other people's records out of a list is not the same as refusing the request when someone supplies another record's identifier. OWASP advises verifying the user's permission every time an access attempt is made.
A short manual check can reveal an obvious gap. Run it only in an isolated test copy of the app that you are authorized to test, using invented records. Never use a real customer's record for this. This manual check cannot prove the app is secure, and it does not replace a security review. Passing it means only that these two users and these records behaved as expected.
- Create two test users with the same role, Test User A and Test User B, and give each one an invented record, such as Sample Booking A and Sample Booking B.
- Sign in as Test User A and open, and where the role allows it edit, A's own record. Each action should be allowed.
- Still as A, try to reach B's record through its normal screen and through the address or reference the app uses for it. Each attempt should be refused, with the clear message you chose for other refusals.
- Repeat with the users swapped, and write the allowed or refused result next to the expected result in your table.
Check the rules before launch
The OWASP sheet describes simple unit and integration tests as an important tool for finding and fixing authorization problems quickly, while noting that they cannot replace manual testing by a skilled tester. For a small app, a manual pass is a sensible start: create one test user per role, then attempt every row of your table, including actions that should be refused. Record what happened next to the expected result.
Repeat the pass whenever a role or action changes. Also decide what should happen when a refusal occurs, such as a clear message and a note in the activity record, so that a person is never left guessing.