The ERP Security Model

ERP authorisation is more granular than typical application security, because a single user might legitimately create purchase orders for one plant, view financials for one company code, and have no access to payroll at all.

The dimensions of ERP authorisation

WHO      user, role, group
WHAT     object/entity — sales order, GL account, employee record
ACTION   create, read, update, delete, post, approve, release, reverse
WHERE    company code, plant, sales org, cost centre, warehouse
WHICH    a subset of rows — my customers, my region, my cost centre
FIELD    which attributes — see the order but not the margin
WHEN     time-bounded access, emergency elevation

Most application frameworks handle WHO/WHAT/ACTION. ERP needs all seven.

The layered model

User
 └── Roles (one user, several roles)
      └── Authorisations / permissions
           └── Objects with field-level values
                └── Organisational restrictions

Worked example:

Role: Purchasing Clerk — Dhaka

  Object: Purchase Order
    Activity:      Create, Change, Display     (not Delete, not Release)
    Purchasing org: 1000
    Purchasing group: 101, 102
    Plant:          DHK1, DHK2                 (not CTG1)
    Document type:  NB (standard), not FO (framework)
    Value limit:    up to 500,000 BDT

  Object: Vendor Master
    Activity:      Display only
    Company code:  1000

  Object: GL Account
    Activity:      Display
    Company code:  1000

That’s one role. A real system has 50–300 roles, and designing them is a specialist job.

Role design

Approaches

Approach Description Verdict
Job-based (single roles) One role = one job. “AP Clerk” contains everything an AP clerk needs Simple, but proliferates as jobs vary
Task-based (composite roles) Small task roles combined into composite roles per job Recommended. Flexible and maintainable
Derived roles A master role defines the activities; derived roles vary only the org values Essential for multi-site. One master, N sites
Ad hoc Grow organically from requests The default failure mode

The recommended structure:

Master role: PO_CLERK_MASTER          (defines all activities and objects)
  ├── Derived: PO_CLERK_DHAKA         (org values: plant DHK1, DHK2)
  ├── Derived: PO_CLERK_CHATTOGRAM    (org values: plant CTG1)
  └── Derived: PO_CLERK_DUBAI         (org values: plant DXB1)

Change an activity once in the master → propagates to all derived roles.

This is what makes a global rollout maintainable. Without derived roles, adding a country means duplicating and hand-editing dozens of roles.

Naming convention

Z_<AREA>_<FUNCTION>_<LEVEL>_<ORG>

Z_MM_BUYER_DISPLAY_ALL
Z_MM_BUYER_CREATE_DHK
Z_FI_AP_CLERK_1000
Z_SD_ORDER_ENTRY_EXPORT

Consistent naming makes the role catalogue navigable and makes SoD analysis possible. Inconsistent naming makes both impossible.

Row-level security

Restricting which records, not just which type:

Sales rep sees only their own customers:
   WHERE sales_rep_id = :current_user

Plant manager sees only their plant:
   WHERE plant_id IN (:user_authorised_plants)

Manager sees their team's records:
   WHERE employee_id IN (SELECT id FROM employee WHERE manager_id = :current_user)

Implementations vary: SAP uses authorisation objects checked in code and structural authorisations in HR; Odoo uses record rules with domains; Frappe uses User Permissions; PostgreSQL-based systems can use Row Level Security policies.

The organisational dimension is the primary row-level filter in most ERPs. Company code, plant, sales org, and purchasing org values on the role determine which rows a user sees. Design your custom tables with those columns so the same mechanism works.

Field-level security

Sales order:
  Order value          — visible to all sales users
  Cost / margin        — visible to managers only
  Customer credit limit — visible to credit team only

Employee record:
  Name, department, extension — visible to all
  Salary, bank details        — visible to HR and the direct manager only
  Health/medical              — visible to occupational health only

Field-level security is more work but genuinely necessary in HR, and increasingly in sales (protecting margin data) and finance.

Critical authorisations to control tightly

Authorisation Risk
Post to GL directly Bypass controls, misstate accounts
Change vendor bank details Payment redirection fraud
Release payment runs Direct theft
Create/change vendor master Fictitious vendor fraud
Change customer credit limit Bad debt exposure
Reverse posted documents Cover up errors or fraud
Change posting periods Post to closed periods
Change configuration in production Anything
Debug/execute arbitrary code Total system access
Table maintenance (direct edit) Bypass all business logic
User administration Grant themselves anything
Run OS commands / read files System compromise
Change master data for pricing Revenue leakage
Approve their own requests Defeats every control

Table maintenance and debug authorisation in production are the two most commonly over-granted and most dangerous. Debug-with-replace lets a developer change variable values at runtime, bypassing every check in the code. It should not exist in production for anyone.

Superuser and emergency access

SAP_ALL / sysadmin / Administrator
  = complete access to everything

Rules:
  - Not assigned to any named user in production, ever
  - Firecall / break-glass account, checked out through a controlled process
  - Every use: requested, approved, time-limited, fully logged, reviewed after
  - Session recording where possible
  - Automatic revocation after the window

This is what Privileged Access Management (CyberArk, SAP GRC Emergency Access Management, Delinea, One Identity) exists for. The auditor will ask: who has superuser access, how many times was it used last quarter, and who reviewed each use?

“The consultant needs SAP_ALL to do their job” is never true and always an audit finding.

Service and technical accounts

Frequently the weakest link:

❌ One shared "INTERFACE_USER" with wide permissions, password in a config file,
   never rotated, used by 14 interfaces

✅ One named account per interface
   Least privilege — only the objects that interface actually touches
   Credentials in a secrets manager, rotated
   Non-interactive (cannot log into the UI)
   Usage logged and monitored
   Ownership documented

When an interface account is shared, you lose attribution. “Which system posted this journal?” becomes unanswerable, and that’s an audit finding on its own.

User lifecycle

JOINER   HR hire event → provision account → assign role per position
MOVER    HR transfer  → REMOVE OLD ROLES → assign new roles
LEAVER   HR termination → disable immediately → delete after retention

The MOVER case is where access accumulates. Someone moves from Purchasing to Finance, gains Finance roles, and keeps Purchasing roles — and now has an SoD conflict nobody noticed. Role removal on transfer must be automatic, not a request someone might raise.

Standard reviews:

  • Access recertification — managers confirm their team’s access quarterly or half-yearly
  • Orphan account report — accounts with no matching active employee
  • Dormant account report — no login in 90 days
  • Privileged access review — monthly

Authentication

Method Notes
SSO via SAML/OIDC The standard. Central control, MFA at the IdP
MFA Mandatory for privileged access; increasingly for everyone
Certificate-based Some high-security environments
Passwords Fallback only; strong policy required

Integrate the ERP with the corporate IdP (Entra ID, Okta, Ping, Keycloak). Benefits: one termination action disables all access, consistent MFA, and central session policy.

Session management: timeout for inactivity (30 minutes is typical, shorter for privileged), concurrent session limits, and forced re-authentication for sensitive actions like payment release.

Logging and monitoring

What must be logged:

- Authentication: success and failure, with source IP
- Authorisation failures (a burst indicates probing or a role gap)
- All changes to master data, with before/after values
- All changes to configuration
- All privileged/emergency access use
- All changes to user access and roles
- Access to sensitive data (salary, personal data) — reads, not just writes
- All postings, with user and timestamp
- Data exports and downloads
- Direct table modifications (if they are possible at all)

Ship these to a SIEM. Alerts worth having:

- Privileged access used outside a change window
- Vendor bank detail changed
- User granted a critical authorisation
- Mass data export
- Login from an unusual location for that user
- Repeated authorisation failures
- Posting to a closed period
- Direct table modification

Developer responsibilities

Every custom object must check authorisation:

❌ A custom report that reads every company code because
   the developer forgot to check the user's org authorisation

✅ Explicit authorisation check on entry, and the org filter
   applied in the query — not after fetching everything

Checklist for every custom object:

  • Authorisation check performed before any data access
  • Organisational scope applied in the WHERE clause, not in application code afterwards
  • Sensitive fields suppressed if the user lacks field-level access
  • Failure produces a clear message, not a dump or an empty screen
  • The check is tested — with a restricted user, not with an admin account
  • No hardcoded credentials, ever
  • SQL injection impossible (parameterised queries)
  • Export functions respect the same authorisation as the screen

That last one is a frequent gap. A report correctly restricts what’s displayed, and the “export to Excel” path queries the table directly with no filter.

Next