Skip to main content
Israr Ahmed Labs

Security & Access Control

Separation of duties in business software: the person who raises a request should not approve it

Most businesses have the rule on paper. Software rarely enforces it. How to build approvals so the person who asks cannot also approve, even by shortcut.

Israr Ahmed · · 3 min read

Most small businesses have a rule that the person who asks for money should not be the person who approves it. On paper this is easy. In software it is often missing, because the first version of an approval screen only asks "does this user have the approve permission?". The owner, who has every permission, then ends up approving their own requests. Separation of duties has to be a rule the software enforces, not a habit people are expected to keep.

The rule, stated precisely

For a workflow with a request and an approval, the person who raised the request cannot be the person who approves it. That one sentence needs three decisions:

  • What counts as "raised"? The person who created the record, or also the one who last edited it or submitted it? Choose, write it down and apply it everywhere.
  • Who may override it? In a business with one or two staff there may be nobody else to approve. You can allow a named top role to approve their own request, but record it clearly as an exception.
  • What happens when the approver is away? Allow a delegate, recorded as acting for them, rather than sharing a login.

Enforce it where the decision is made

A hidden button is not enforcement. Put the rule in the one function that performs the approval, so every path goes through it: the page, the form action, a bulk action and any future interface.

function approve(user, expense):
  if expense.status is not "submitted": refuse
  if user is the person who raised it and is not the top role: refuse
  mark approved, record who and when

Write the rule as a small function that takes the user and the record and returns yes or no, with no database access, and unit-test the awkward cases: the requester, the requester who is also the top role, a user who holds the permission but cannot see the record, and a disabled approver.

Make every step a recorded transition

Model the workflow as states with allowed moves: draft, submitted, approved, paid. Each move is one function that checks the rule, changes the status, and writes an audit entry saying who moved what, from which state to which, and when. Money records should be corrected with a reversing entry, not by editing or deleting the original. The history is what an auditor, or you in a year's time, will read.

Say why it was refused

When the system refuses, tell the person the reason: "You raised this request, so someone else must approve it." A silent failure makes people look for a workaround.

Test it from outside the interface

Sign in as the requester and call the approval endpoint directly. If it succeeds, the rule lives only in the interface. Test it the way a careless colleague or a stolen session would use it.

Where this applies

In the management systems we build, expenses, leave and document approvals follow the same pattern: separate permissions for raising and approving, a record-level check on top of the permission, a delegate when the approver is away, and an audit trail. The cost is small. The benefit is that the control keeps working when the business grows beyond the people who trust each other.

  • security
  • approvals
  • audit
  • business software

Updated . Need help with something like this? Get in touch.

Have a project or a problem to solve?

Tell us what you are trying to achieve. We will reply by email with questions or a suggested next step.