User roles and permissions: planning admin, staff and customer access

How to plan who can see and change what in a small business app, with a permissions matrix you can copy, the difference between roles and ownership rules, and how to test it before launch.

Floot ·

Key Takeaways

  • Roles answer “what kind of person is this?”. Admin, staff and customer are roles. Each role gets a set of actions, such as viewing all orders or editing prices.
  • Ownership answers “is this theirs?”. Most real rules combine both: a customer can view orders, but only orders where they are the customer.
  • Deny by default. OWASP’s guidance is that applications should deny access unless a rule explicitly allows it. New tables and screens start closed.
  • Check on the server, every time. Hiding a button is not a permission. The server must check every request, because anyone can call it directly.
  • Test as each role. Sign in as an admin, a staff member and two different customers, and try to reach each other’s data by changing IDs in the address bar.

Quick-Start Prioritization Framework

RoleTypical scopeTypical limits
Owner or adminEverything, including settings, users and deletionKeep to one or two people
ManagerAll operational records for their team or locationNo billing, no role changes
StaffRecords assigned to them, plus shared reference dataNo exports, no deleting
Customer or clientOnly their own recordsRead most things, edit only their profile and requests
Public visitorPages and forms you choose to exposeNo access to stored data

The moment a second person signs in to your app, you have a permissions problem to solve. Staff shouldn’t see each other’s pay, customers shouldn’t see each other’s orders, and only you should be able to delete things. Getting this wrong is common enough that OWASP, the web security nonprofit, ranks broken access control first in its 2025 Top 10 list of web application risks. The good news is that a small app’s permissions fit on one page if you plan them before building. This guide shows how.

Roles, permissions and ownership: three different ideas

People often use these words interchangeably, which is where confusion starts. Pull them apart and the plan becomes clear.

A permission is an action on a kind of data

View invoices, create appointments, edit prices, delete customers, export data. Write permissions as verb plus noun.

A role is a named bundle of permissions

Instead of granting fifteen permissions to each new staff member, you give them the staff role. This is called role-based access control. It keeps the system understandable: when you change what staff can do, every staff member changes at once.

Ownership limits a permission to certain rows

“Staff can view jobs” is rarely what you mean. You mean “staff can view jobs assigned to them.” That condition depends on the record, not on the role, so it needs a column in the data, such as assigned_to or customer_id, that the server compares with the signed-in user.

Build a permissions matrix

A matrix is a table with your data down the side and roles across the top. Each cell says what that role can do with that data. Here is one for a small home-cleaning business with an office admin, cleaners and clients.

DataAdminCleanerClient
ClientsView, create, edit, archiveView clients on their assigned jobsView and edit own profile
JobsAll actionsView and update status on own jobsView own jobs, request a new one
Job notes and photosAll actionsAdd to own jobsView notes marked as shared
Invoices and paymentsAll actionsNoneView and pay own invoices
Cleaner pay ratesView and editView own rate onlyNone
Users and rolesInvite, change role, deactivateNoneNone
ExportsFull exportNoneDownload own invoices

How to fill it in

Start every cell at “None,” then add only what each role needs to do its job. OWASP describes this as least privilege: assigning users “only the minimum privileges necessary to complete their job.” It’s far easier to grant a missing permission when someone asks than to discover later that everyone could see payroll.

Split view and edit

Many rows in the matrix will have different answers for viewing and changing. A client can see their job’s scheduled time but not move it; a cleaner can mark a job done but not change its price.

Choosing roles for a small app

Fewer roles is better. Each extra role multiplies the testing you need and the chances of an inconsistent rule.

Start with three

Admin, staff and customer cover most small businesses. Add a role only when a real person needs a combination that none of the existing roles gives, such as a bookkeeper who needs invoices but no client notes.

Separate the owner from daily admin

Destructive and sensitive actions, like deleting data, changing roles, connecting payments or exporting everything, can sit with an owner role held by one or two people. Day-to-day admins handle bookings and records without those powers.

Teams and locations

If you have several locations, managers usually need “all records for my location.” That’s an ownership rule on a location_id column, not a new role per location. One manager role plus a location column scales; a role per branch doesn’t.

Where permissions must be enforced

This is the part that separates a safe app from one that only looks safe.

The interface is a convenience, not a lock

Hiding the Delete button from staff makes the app tidier. It doesn’t stop a staff member from sending a delete request directly. OWASP’s cheat sheet is blunt: developers “must never rely on client-side access control checks.” The server, or the database, has to check.

Check every request

OWASP also advises that permission “should be validated correctly on every request.” Not just at sign-in, and not just on the main page. Every endpoint that reads or writes data must confirm both the role and the ownership condition.

Changing an ID is the classic attack

OWASP’s 2025 Top 10 describes an attacker changing an account number in a URL to see someone else’s account. If your invoice page is /invoices/1042 and changing it to /invoices/1043 shows another client’s invoice, the server isn’t checking ownership. Random-looking IDs make guessing harder but are not a fix on their own; the check is.

Database-level rules

Some setups enforce ownership inside the database itself. PostgreSQL’s Row Level Security, for example, attaches rules to tables; Supabase’s documentation tells you to enable it on every table in an exposed schema, because without it any role with a grant can read and write the table. Whether the check lives in server code or the database, it must exist for every table.

Inviting, changing and removing access

Permissions aren’t set once. People join, change jobs and leave, and each of those moments needs a defined process.

  • Invite staff by email with the role already chosen, so nobody signs up as admin by accident.
  • Let customers sign up themselves, but always as the customer role.
  • Only owners can promote someone to admin.
  • Deactivate rather than delete departing staff, so their past work keeps its history.
  • Review the user list every quarter and remove accounts nobody recognizes.

Shared logins

A single “frontdesk@” login shared by four people means you can’t tell who did what or remove one person’s access. Give everyone their own account; it’s worth the small cost of extra sign-ups.

How to test permissions before launch

Plan an hour for this. It’s the most valuable hour of the whole build.

Create one test account per role, plus a second customer

You need two customers so you can check that Customer A can’t see Customer B. Give each some sample records.

Walk the matrix

For each cell of the matrix, sign in as that role and try the action. Then try the actions marked None. Note anything that succeeds when it shouldn’t.

Try the URL and ID test

Signed in as Customer A, open one of Customer B’s record links, or change the number in an address like /orders/57. You should get a not found or access denied page, not the data.

Check exports, search and emails

Leaks often come from side doors: a search box that searches every record, an export button that ignores filters, a notification email that includes another customer’s details.

Common mistakes

Most permission bugs in small apps come from the same few habits.

Everyone starts as admin

Builders test as themselves, which means as the admin. Always test as a newly signed-up customer.

Rules in the interface only

Hidden buttons and filtered lists without server checks are the most common cause of data leaks.

Role per person

Creating a custom role for each employee becomes unmanageable. Use a few roles plus ownership columns.

Forgetting new tables

A table added months later without rules is open by default in some setups. Deny by default, and add each new table to the matrix.

If you’d rather not build this yourself

If you’d rather not wire up accounts and checks yourself, AI app builders such as Floot give apps their own sign-up and login (email, Google or Microsoft) with roles. Hand it your permissions matrix and then test it with the steps above, as you would any app.

Frequently Asked Questions

What is the difference between a role and a permission?

A permission is a specific action on a kind of data, such as editing invoices. A role is a named group of permissions, such as staff, that you assign to people so you don’t manage each permission individually.

How many user roles should a small business app have?

Start with three: admin, staff and customer. Add a role only when someone needs a combination of permissions none of the existing roles gives. Use ownership rules, not extra roles, for “only their own records.”

Is hiding buttons enough to protect data?

No. OWASP’s guidance is to never rely on client-side access checks. Every request has to be checked on the server or in the database, because requests can be sent without using your interface.

What is role-based access control (RBAC)?

A way of managing access where permissions are attached to roles, and people are given roles. Changing a role’s permissions updates everyone who holds it.

How do I make sure customers see only their own data?

Store the customer’s ID on every record that belongs to them, and have the server only return records where that ID matches the signed-in user. Test with two customer accounts and by changing IDs in URLs.

What is broken access control?

It means users can act outside their intended permissions, such as viewing another person’s account by changing an ID. OWASP ranks it first in its 2025 Top 10 web application security risks.

Sources