Maker-checker and SoD: why roles and permissions are not authorisation
A matrix of roles and checkboxes answers one of the six questions ERP authorisation has to settle — the other five usually end up in a spreadsheet, a policy nobody reads, and the head of the chief accountant.
- Published
- Author
- Konis Software
Almost every ERP we have met at a client site has a screen called Users and Permissions: roles against modules, checkboxes for view, create, edit and delete. That screen is not wrong. It is the first of six layers, and usually the only one enforced server-side.
What roles cannot answer
Take a sentence from any distributor's internal policy: a sales rep may approve a discount up to 8%, above that the sales director, and whoever prepared a payment order must not release it. That sentence holds four questions. RBAC answers one.
- May this user approve discounts at all? A question of role — the only one the checkbox matrix settles.
- Up to what amount? A question of limits, depending not on the user but on the document.
- On which documents? A question of data scope — the rep covering the west has no business with eastern orders, yet holds the same role.
- Is this the person who raised the document? A question of SoD: the answer lives in the document's history, not the profile.
With only the first layer, the other three are not lost — they relocate. Limits move to a spreadsheet kept by somebody in finance. Data scope becomes an understanding that people do not open each other's documents. Segregation of duties becomes a sentence in a policy signed yearly. All three are unverifiable. When an auditor asks how the system stops one person creating a supplier, changing its bank account and paying it, a policy is not an answer.
Six layers
Authorisation that survives an audit is six checks executed in order, on the server, on every action. Not alternatives — the first that refuses stops the action, and the reason is written to the journal with the layer that raised it.
- 1
Roles and permissions (RBAC)
What a user may do as a type of action: raise a purchase order, post a journal entry, edit a price list. It knows nothing about amounts, counterparties or history.
- 2
Data scope
Which rows the permission covers: legal entity, warehouse, department, cost centre. A database decision, not an interface filter — if scope lives in the query the front end sends, any REST client walks around it.
- 3
Approval limits
Up to what amount, along which dimension: a discount up to 8%, a write-off up to the warehouse manager's ceiling. Computed from the document at the moment of decision, under that day's rule.
- 4
Segregation of duties (SoD)
Which powers must never gather on one account, and which two actions one person must never perform on one document. The only layer that looks backwards.
- 5
Maker-checker
When a second pair of eyes is required before an action becomes real. A limit asks how much; maker-checker asks who else.
- 6
Delegation and cover
What happens when the approver is on leave. It looks like housekeeping and is the hole the other five drain through.
| Layer | Question it answers | Where it breaks when absent |
|---|---|---|
| Roles and permissions | Is this action allowed? | Nowhere — everybody has it |
| Data scope | On which rows? | One role sees every legal entity |
| Approval limits | Up to what amount? | A spreadsheet, outside the trail |
| Segregation of duties | May these two combine? | One account creates, edits, pays |
| Maker-checker | Is a second person required? | Critical actions pass with no record |
| Delegation | Who approves while they are away? | A shared manager password |
SoD is not a list of roles that do not mix
SoD is most often built as a static matrix: roles A and B cannot sit on the same user. That is the easier half. The static check runs at role grant and catches the administrator's mistake; the dynamic check runs at the action and catches what SoD exists for. Statically, one user may hold both a purchasing and an invoice-clearing role — smaller companies cannot operate otherwise. Dynamically, that user must not clear the invoice they entered. The first question is about the organisation, the second about one document.
The pairs that recur in practice:
- Editing supplier master data and releasing a payment. The account changes, the money reaches somebody else, and the order still carries a correct reference number and valid QR code.
- Receiving goods and posting the supplier invoice. Without separation, three-way matching is theatre — one person sets both compared sides.
- Counting stock and posting the variance. A shortage that becomes an internal VAT charge must not be approved by the counter.
- Running payroll and approving the disbursement. The tax filing and the bank instruction are not one action.
- Editing a price list and approving the revaluation. In retail a revaluation changes inventory value.
- Creating a user account and assigning roles. An administrator who may self-grant a role holds every role.
The last item explains why SoD is checked twice. Check it only at role grant, and the conflict is assembled over time: add the role, act, remove it — the Friday-evening snapshot looks clean. Check it only at the action, and the matrix never reports the incompatible combinations waiting for an occasion.
A control checked only when a permission is granted measures a state. A control checked only at the action measures an event. Fraud happens between the two measurements.
Maker-checker: a second pair of eyes, but only where it earns its keep
Maker-checker is the rule that an action does not become real until a second person confirms it. It sounds like approval but ignores the amount entirely: it asks whether this action is one that a single person must never perform alone.
Those actions: changing a counterparty's bank account, reversing a posted document, correcting inventory by hand, reopening a closed period, editing a tax mapping, deleting anything. They carry no amount, or one deceptively small next to the consequence — changing a field in master data crosses no threshold. The decision record is most often done badly; to mean anything a year later, it must hold:
- Who proposed, who confirmed, when — in UTC, with the user's time zone stored separately.
- What the confirmer saw then: old and new value, amount, counterparty, rule version. Not a reference to a document that changed ten times since.
- Why — a mandatory reason on rejection, and above a threshold the customer defines.
- From which context: session, IP, and whether it arrived through a screen, an API or a rule.
- Which layer demanded confirmation, under which configuration record — configuration changes, and the journal must stay readable.
Delegation — the layer that dismantles the other five
The sales director goes on leave and purchase orders above the threshold stop. With no delegation, the workaround is always the worst one: somebody is given the password. The journal then records that the director approved from the coast, and that is the only trace. Delegation is a security mechanism, not a calendar convenience:
- A delegate never receives more than the delegator holds. Delegation transfers a right, it does not enlarge one.
- Delegation must not route around SoD. If the delegate raised the document, the delegated right does not apply — permissions get checked, provenance does not.
- Delegation expires by itself. Without an end date, nobody knows who really approves what.
- The journal keeps both names: the delegate approved, on behalf of the delegator — two fields, never one.
- Delegation is approved, not self-assigned. Transferring authority is itself worth a maker-checker.
The immutable journal
The five layers work forwards and prevent an action. The journal works backwards and answers the question that comes months later: who, what, when, under which rule. Immutable is not decorative — a journal whose rows the system may edit is a table. No UPDATE, no DELETE, written in the same transaction as the action, because an asynchronous journal goes silent about exactly the actions that failed.
| Record | What it holds | Why that specifically |
|---|---|---|
| Business action | Who, what, when, against what, from where | Without context a human looks like an integration |
| Configuration change log | Old and new value, who, when, version | A setting alters every future document |
| Document state history | The transition, the guard, five dates | Document, posting, due and supply dates differ |
| Decision record | What the approver saw then | The document changes after approval; the decision does not |
| Export record | Who, which data, how many rows | The quietest way for data to leave |
The fourth row is most often skipped and costs most. If an approval stores only a reference to the document, a year later the auditor opens an approval for 300,000 and finds a document for 2.4 million, because a line was added afterwards. It is formally present and proves nothing. So the decision record carries a snapshot of the values as they stood, and editing a document after approval must void it.
How NG One arranges these six layers
In NG One authorisation sits in the core, alongside multi-tenant isolation enforced by row-level security in the database and the document framework — an ordering of necessity: a layer absent while the first use case is written means every use case up to that point was written without it, and retrofitting SoD into three hundred screens is a rewrite, not an upgrade. So all six checks run in the service layer, behind the same API the interface uses: the same decision stands whether the action comes from a screen, an OpenAPI client or an MCP tool. What counts as critical — which actions need maker-checker, where thresholds sit, which pairs conflict — is the customer's configuration, versioned, with a change log that keeps the old value and the new. The exception is the platform side, where the list is fixed because it constrains us, not the customer.
Authorisation is not a list of what someone is allowed to do. It is the answer to whether this action, on this document, by this person, now, may happen at all — and the proof, a year later, that the answer was right.