Authorization Models
How systems decide what an authenticated identity may do: access control lists, role-based and attribute-based access control, entitlements, least privilege and separation of duties.
Authorization after authentication
Authentication establishes who is asking; authorization decides what they may do. Every authorization decision has the same ingredients: a subject (the user, group or workload), an action (read, change, delete, approve), a resource (a file, a mailbox, a router, a subscription) and sometimes a context (time, location, device, risk). An authorization model is the logic that turns those ingredients into "allow" or "deny".
The general access control models (discretionary, mandatory, role-based and attribute-based) are introduced in Access Control. This page looks at how they work in identity systems and directories, and how to combine them without losing control.
Permissions and entitlements
A permission is the smallest unit of access: one
action on one kind of resource, such as "reset passwords of users in
OU Rotterdam" or "read objects in storage account
kestrelbilling". Permissions on their own are too
fine-grained to manage, so systems bundle them.
An entitlement is any access right that has been granted to an identity in some system: a group membership, an application role, a license, a SharePoint site membership, a role in a SaaS tool. Identity governance thinks in entitlements because that is what can be requested, approved, reviewed and removed. "What can this person do?" in practice means "list all their entitlements, in every system".
Entitlements are easy to grant and easy to forget. Each one should have an owner (who decides who gets it) and a reason (why this identity has it), or the next access review cannot judge it.
Access control lists
The oldest model attaches the rules to the resource. An access control list (ACL) lists, for one object, which subjects may do what with it. On Windows, every file, folder, registry key and Active Directory object has one (see NTFS permissions); on Linux, the permission bits plus optional POSIX ACLs play that role.
ACLs are precise, but they scale badly when they name individual users. With 500 shares and 2,000 users, "who can read what" is spread over 500 lists. The standard fix is to put only groups in ACLs and manage people through group membership, which turns ACLs into a building block of RBAC (see Users, Groups and Computers for the AGDLP nesting pattern).
Role-based access control in depth
In role-based access control (RBAC), permissions are granted to roles and roles are assigned to identities. A role should describe a job function ("service desk analyst", "billing clerk", "NOC engineer"), not a person.
Three parts make up a role assignment in modern platforms:
- Who: the user, group or workload (security principal).
- What: the role definition, the list of allowed actions.
- Where: the scope at which the role applies, such as the whole directory, one administrative unit or OU, one subscription or one resource group.
Scope is what makes RBAC precise without inventing extra roles. A "Helpdesk Administrator" for the Rotterdam office and one for Amsterdam use the same role definition with different scopes.
RBAC's strengths are that it is easy to explain and to audit ("who has the billing clerk role?") and that a move between jobs is a matter of swapping roles. Its weakness is that it cannot express conditions: "billing clerks may read invoices, but only of their own region, and only from a company laptop" does not fit a plain role.
Attribute-based access control in depth
Attribute-based access control
(ABAC) evaluates rules over attributes of the subject, the resource,
the action and the environment. The attributes often come straight
from the directory: department,
employeeType, country. Examples from real
platforms:
-
Azure role assignments can carry conditions, for example "may read
blobs only when the blob's
projecttag equals the user'sprojectattribute". - AWS policies use condition keys and tags in the same way (see cloud policies).
- Conditional access in an identity provider is attribute-based in spirit: user, device state, location and sign-in risk decide whether access is granted, and how.
ABAC is flexible: one rule ("same department as the resource") can replace dozens of department-specific roles. The price is transparency. To answer "who can read this?" you must evaluate the rule against every identity, and a wrong attribute in HR (the wrong department) silently grants or removes access. ABAC is only as trustworthy as the attributes it reads.
Combining roles and attributes
In practice the models are layered: the role decides whether an action is possible at all, attributes narrow it down. This decision function first checks roles, then three attribute rules: the resource must belong to the user's department, sensitive resources need a managed device, and configuration changes outside office hours need a change ticket. It returns the decision with the reasons, because a denial that cannot be explained produces a ticket that cannot be solved.
# RBAC: which actions each role grants on which resource type
ROLE_PERMISSIONS = {
"service-desk": {("user-account", "reset-password"), ("ticket", "update")},
"noc-engineer": {("network-device", "read-config"), ("network-device", "change-config")},
"billing-clerk": {("invoice", "read"), ("invoice", "create")},
}
def decide(user, action, resource, context):
"""Return (allowed, reasons). RBAC first, then ABAC conditions."""
reasons = []
granted_by = [r for r in user["roles"]
if (resource["type"], action) in ROLE_PERMISSIONS.get(r, set())]
if not granted_by:
return False, [f"no role grants {action} on {resource['type']}"]
reasons.append(f"role {granted_by[0]} grants {action}")
# ABAC: attributes of the resource, the subject and the environment
if resource.get("department") and resource["department"] != user["department"]:
return False, reasons + [f"resource belongs to {resource['department']}, user to {user['department']}"]
if resource.get("sensitivity") == "high" and not context["managed_device"]:
return False, reasons + ["high-sensitivity resource requires a managed device"]
if action == "change-config" and not 7 <= context["hour"] < 19 and not context.get("change_ticket"):
return False, reasons + ["config changes outside 07:00-19:00 need a change ticket"]
return True, reasons + ["all attribute conditions met"]
noc = {"name": "noc engineer", "roles": ["noc-engineer"], "department": "NOC"}
desk = {"name": "service desk analyst", "roles": ["service-desk"], "department": "Service Desk"}
router = {"type": "network-device", "department": "NOC", "sensitivity": "high"}
clerk = {"name": "billing clerk", "roles": ["billing-clerk"], "department": "Customer Service"}
invoice = {"type": "invoice", "department": "Billing"}
requests = [
(noc, "change-config", router, {"hour": 10, "managed_device": True}),
(noc, "change-config", router, {"hour": 23, "managed_device": True}),
(noc, "change-config", router, {"hour": 23, "managed_device": True, "change_ticket": "CHG-0412"}),
(noc, "read-config", router, {"hour": 10, "managed_device": False}),
(desk, "read", invoice, {"hour": 10, "managed_device": True}),
(clerk, "read", invoice, {"hour": 10, "managed_device": True}),
]
for user, action, resource, context in requests:
allowed, reasons = decide(user, action, resource, context)
print(f"{'ALLOW' if allowed else 'DENY ':5} {user['name']}: {action} at {context['hour']:02}:00")
for reason in reasons:
print(" -", reason)
ALLOW noc engineer: change-config at 10:00
- role noc-engineer grants change-config
- all attribute conditions met
DENY noc engineer: change-config at 23:00
- role noc-engineer grants change-config
- config changes outside 07:00-19:00 need a change ticket
ALLOW noc engineer: change-config at 23:00
- role noc-engineer grants change-config
- all attribute conditions met
DENY noc engineer: read-config at 10:00
- role noc-engineer grants read-config
- high-sensitivity resource requires a managed device
DENY service desk analyst: read at 10:00
- no role grants read on invoice
DENY billing clerk: read at 10:00
- role billing-clerk grants read
- resource belongs to Billing, user to Customer Service
Note the order: the function denies as soon as one rule fails and allows only when every rule passed (deny by default). Note also the last case: the billing clerk has the right role, but the attribute check stops access to another department's invoices. That is exactly the condition plain RBAC cannot express.
Least privilege and separation of duties
Least privilege means every identity, human or not, gets only the permissions its task requires, at the narrowest scope, for no longer than needed. It limits what a mistake, malware running under the account or an attacker with stolen credentials can do. The local-computer side is covered in Users and Permissions; in identity systems it means, for example:
- Assigning the Password Administrator role (or Helpdesk Administrator if they also need other helpdesk tasks) instead of Global Administrator to a service desk analyst who resets passwords.
- Scoping that role to the users the analyst actually supports.
- Making admin roles time-limited and activated on request (just-in-time access; see Privileged Access Management).
- Giving an application's service account read access to one OU, not the whole directory.
Separation of duties complements it: no single identity may complete a sensitive process alone. In identity terms, the person who requests access does not approve it, the administrator who creates accounts does not also review them, and nobody holds both "create supplier" and "approve payment" in the finance system. Governance tools can flag such toxic combinations of entitlements before they are granted.
Role explosion
Role explosion is what happens when every exception becomes a new role: "billing clerk", "billing clerk Rotterdam", "billing clerk Rotterdam with export rights", "billing clerk Rotterdam temporary". After a few years there are more roles than people, nobody knows what each role grants, and access reviews turn into rubber-stamping.
How to prevent it:
- Use scope instead of copies. One role definition, assigned at different scopes, rather than a role per location.
- Use attributes for conditions. Move "only own region" or "only from a managed device" into ABAC conditions instead of new roles.
- Separate business roles from technical roles. A business role ("new billing clerk") bundles several technical entitlements (a group in AD, an app role in the billing system, a Teams channel). People get business roles; only the identity team maintains the mapping.
- Give every role an owner and review it. Roles with no members or no owner are removed.
- Grant exceptions with an end date. A one-off need becomes a time-limited assignment, not a permanent role.
Decision and enforcement points
Larger systems split authorization into separate functions. The terms come from the XACML policy standard; NIST's zero trust architecture (SP 800-207) reuses the decision and enforcement points:
- The policy enforcement point (PEP) sits in the path of the request: a gateway, a proxy, the application itself. It intercepts the request, asks for a decision and enforces it.
- The policy decision point (PDP) evaluates the policies against the request and its context and returns allow or deny, possibly with obligations ("allow, but require MFA").
- The policy information point (PIP) supplies attributes the PDP needs: group memberships from the directory, device compliance, risk scores.
- The policy administration point (PAP) is where administrators write and manage the policies.
You meet this split everywhere. In Microsoft Entra, conditional access is the decision point and the token service, applications and proxies enforce its outcome. In a VPN, the gateway enforces and a RADIUS server or the identity provider decides. In an application, a central authorization service can decide while each microservice enforces. The benefit is that policy lives in one place: change the rule once, and every enforcement point follows.
Example: Entra and Azure roles
Microsoft's cloud shows two separate RBAC systems side by side, which regularly confuses newcomers:
| Aspect | Microsoft Entra roles | Azure roles (Azure RBAC) |
|---|---|---|
| Controls | The directory: users, groups, applications, authentication settings | Azure resources: virtual machines, storage, networks, databases |
| Built-in examples | Global Administrator, User Administrator, Helpdesk Administrator, Security Reader | Owner, Contributor, Reader, User Access Administrator |
| Scopes | Tenant, administrative unit, single application | Management group, subscription, resource group, resource |
Being Global Administrator in Entra does not by itself give access to virtual machines in a subscription, and being Owner of a subscription does not let you reset passwords. Assignments and scopes for Azure resources are explained in Identity and Access Management; Entra's directory roles in Microsoft Entra ID. Both support custom roles when no built-in role fits, but a custom role is something to maintain, so check the built-in list first.
Common mistakes
- Granting to users, not groups or roles. Direct assignments are invisible in role reviews and stay behind when people move.
- Using an admin role because a narrower one was hard to find. Global or Domain Admin "to make it work" is the most common least-privilege failure.
-
Trusting attributes nobody maintains. An ABAC
rule on
departmentis only correct if HR updates the department when people move. - Explaining nothing in a denial. Log why access was denied (which rule failed), so the service desk can solve the ticket without guessing.