01Lab Metadata
| Field | Value |
|---|---|
| Lab ID | IB-SIA-13 |
| Track | Identity Bytes, Senior IAM Architect Track |
| Phase | Phase 3, Modern Authorization |
| Difficulty | Intermediate |
| Estimated Time | 4 to 5 hours |
| Core Technologies | RBAC (NIST model), ABAC (NIST SP 800-162), Keycloak Authorization Services, UMA 2.0 grant, Python 3.11, curl, jq |
| Builds On | Lab 01 (users and group attributes in OpenLDAP), Lab 02 (realm roles and client configuration), Lab 09 (reading the permission claims in the returned token) |
| Feeds Into | Lab 14 (OPA/Rego, externalising the policy decision), Lab 15 (ReBAC/OpenFGA), Lab 16 (externalised authorization at the edge) |
02Lab Title and Description
RBAC to ABAC: From Roles to Attributes
For most of its history Northgate Financial has controlled access with roles. A user is a member of finance-team or it-admins, a role grants a fixed set of permissions, and access is decided by asking a single question: does this user have this role? Role-Based Access Control is simple, auditable and widely understood, and for a stable organisation it works well. Then Northgate acquired Harborview Wealth, and the model began to strain. Harborview had its own departments, its own seniority tiers, and rules that depend on things a role cannot capture: a wealth adviser may view a client portfolio only if it belongs to their own book, only during market hours, and only up to their approval limit. Expressing that with roles alone means inventing a new role for every combination, and the number of roles began to grow faster than anyone could govern.
This is role explosion, and it is the classic signal that an organisation has outgrown pure RBAC. The answer is Attribute-Based Access Control (ABAC), described in NIST SP 800-162. Instead of asking only "what role does the user have", ABAC evaluates a policy over attributes of the subject (department, clearance, book of business), the resource (owner, classification, value), the action, and the environment (time of day, network, risk score from Lab 08). One policy can then replace dozens of narrow roles.
In this lab you first model Northgate's access in RBAC and watch the role count balloon as you try to honour the Harborview rules. Then you rebuild the same decisions using Keycloak Authorization Services, defining resources with attributes, and policies over user attributes and time, combined with aggregate logic. Finally you evaluate authorization decisions for two users who hold the same role but get different answers, because their attributes and context differ, the decision RBAC cannot express. You will also see clearly where Keycloak's built-in policies stop and why a dedicated policy engine, the subject of Lab 14, is the next step.
Estimated completion time: 4 to 5 hours, including RBAC modelling, Authorization Services configuration, and decision testing.
03Prerequisites
Completed Prior Labs
| Lab | Why it is required |
|---|---|
| IB-SIA-01, OpenLDAP directory | Provides the users and the group and attribute structure. This lab adds attributes (department, clearance) that ABAC policies evaluate. |
| IB-SIA-02, Keycloak realm and OIDC federation | Provides the northgate realm and the realm roles used in the RBAC phase, and the client that becomes an Authorization Services resource server. |
| IB-SIA-09, JWT, JWS and JWE Deep Dive | You decode the Requesting Party Token returned by the authorization decision to read the granted permissions. The token-reading skills from Lab 09 are used directly. |
System Requirements
| Resource | Minimum |
|---|---|
| OS | Ubuntu 22.04 LTS, macOS 13+, or Windows 11 with WSL2 |
| RAM | 7 GB free (the Keycloak HA cluster from Lab 06 accounts for most of this) |
| Disk | 2 GB free |
| CPU | 2 cores sufficient |
| Network | Access to the running Keycloak cluster on ib-lab-net |
Required Tools
| Tool | Exact Version |
|---|---|
| Docker Engine | 25.0 or later |
| Python | 3.11.x (for decoding the decision token) |
| curl | 8.x |
| jq | 1.7 |
Install and verify: Ubuntu/Debian
sudo apt update
sudo apt install -y python3.11 jq curl
mkdir -p ~/ib-labs/ib-abac
cd ~/ib-labs/ib-abac
python3.11 --version # Expect: Python 3.11.x
jq --version # Expect: jq-1.7 or later
docker --version # Expect: Docker version 25.x or later
Install and verify: macOS
brew install python@3.11 jq
mkdir -p ~/ib-labs/ib-abac && cd ~/ib-labs/ib-abac
python3.11 --version
jq --version
Install and verify: Windows 11 (WSL2)
# Run inside your WSL2 Ubuntu distribution, not PowerShell
wsl --install -d Ubuntu-22.04
# Then follow the Ubuntu/Debian instructions above.
04Real World Problem Statement
Pure role-based access forces every access rule to be expressed as membership in a group. When rules depend on context, ownership or thresholds, the only way to encode them in roles is to multiply roles until the model becomes ungovernable. ABAC replaces that combinatorial growth with policies that read attributes directly.
Risk
Role explosion produces roles no one fully understands, which leads to over-granting "to be safe" and to orphaned roles that are never revoked. Both are direct paths to excess privilege, the root cause behind a large share of insider and lateral-movement incidents.
Compliance
Access recertification, required under ISO 27001 and FCA expectations, becomes impractical when there are thousands of near-duplicate roles. Reviewers cannot meaningfully attest to grants they cannot understand, which turns recertification into a rubber stamp.
Productivity
Every new business rule under RBAC means a role design change, a provisioning change and a recertification change. Under ABAC, a rule such as "only during market hours" is one policy, changed in one place, without touching any user's role assignments.
Security Posture
ABAC lets Northgate express least privilege precisely: access limited to the exact combination of subject, resource and context that the business rule intends, rather than the nearest available role that happens to be broad enough.
Concrete scenario: After absorbing Harborview Wealth, Northgate needed to express: a wealth adviser may read a client portfolio only if the portfolio's owning desk matches the adviser's own desk, and only during UK market hours. In RBAC this became a proliferation of roles like wealth-adviser-desk-a-markethours, one per desk and context combination. In this lab you build that RBAC model, count the roles it demands, then replace it with a single attribute and time policy in Keycloak Authorization Services, and prove two advisers on different desks get different answers for the same portfolio.
05Skills Mapped to Production Solutions
| Skill Learned | Real-World Enterprise Application |
|---|---|
| Recognising and quantifying role explosion in an RBAC model | Diagnosing when an organisation has outgrown pure RBAC, a common finding in IAM maturity assessments and access governance reviews |
| Modelling access as ABAC policies over subject, resource, action and environment attributes | Designing scalable authorization for organisations with contextual, ownership-based or threshold-based rules |
| Configuring Keycloak Authorization Services: resources, scopes, policies and permissions | Implementing fine-grained, centrally managed authorization on Keycloak-protected applications |
| Using the UMA 2.0 grant to request and read an authorization decision | Building resource servers that delegate the access decision to Keycloak rather than hard-coding it |
| Identifying the limits of built-in policy engines | Making the architectural call on when a dedicated external policy engine (OPA, the subject of Lab 14) is warranted |
| Designing hybrid role-plus-attribute models | Pragmatic authorization architecture that keeps roles for coarse grants and attributes for contextual decisions |
06Architecture Overview
Component Breakdown
| Component | Purpose | Technology | Deployment | Ports | Key Configuration |
|---|---|---|---|---|---|
| Policy Decision Point (PDP) | Evaluates policies over attributes and returns permit or deny | Keycloak Authorization Services | Existing HA cluster from Lab 06 | 8443 | Authorization enabled on a confidential client acting as the resource server |
| Policy Enforcement Point (PEP) | Asks the PDP for a decision and enforces it on the protected resource | Resource server (represented here by the UMA ticket flow via curl) | Conceptual in this lab; a real PEP is application code or a gateway | N/A | Requests decisions with the UMA 2.0 grant |
| Policy Information Point (PIP) | Supplies the attributes policies read: user attributes, resource attributes, and the clock | Keycloak user attributes, resource attributes, time policy | Attributes stored on users and resources in Keycloak | N/A | User attributes desk and approval_limit; resource attributes set on the portfolio resource |
| RBAC baseline | The role model built first to demonstrate role explosion | Keycloak realm roles | Same realm | N/A | Roles created and counted in Phase 1 |
Data Flow
- The enforcement point receives a request to act on a resource and asks Keycloak for a decision using the UMA 2.0 ticket grant, presenting the user's access token.
Why: delegating the decision to a central PDP means the rule lives in one governed place, not scattered through application code where it drifts out of sync. - Keycloak gathers the relevant attributes, the user's attributes from the token and directory, the resource's attributes, and the current time.
Why: an attribute decision is only as good as its inputs; the PDP must pull from authoritative attribute sources rather than trusting the caller to supply them. - Keycloak evaluates the permission's policies, combining an attribute policy and a time policy with aggregate logic, and returns permit or deny.
Why: combining policies lets one permission express a compound rule ("right desk AND market hours") that would need many roles to approximate. - The enforcement point honours the decision. On permit it returns a Requesting Party Token listing the granted permissions; on deny it refuses the request.
Why: the token gives the resource server a verifiable statement of exactly what was granted, which it can log for audit alongside the decision.
Security Considerations
| Concern | Lab Approach |
|---|---|
| Attribute integrity | User attributes are set through Keycloak and surfaced as verifiable token claims; the PDP does not trust attributes supplied by the requesting client. |
| Default deny | Permissions default to deny unless a policy explicitly permits, so a gap in policy coverage fails safe. |
| Policy combination | Aggregate policies use explicit AND or affirmative logic, so a compound rule cannot be satisfied by meeting only part of it. |
| Decision auditability | Each decision can be logged with the attributes that drove it, giving a defensible audit trail, which matters more under ABAC where the rule is not visible in a simple role name. |
07Step by Step Implementation
Phase 1: Model RBAC and Hit the Wall
Step 1.1: Build the naive RBAC role set for the Harborview rules
Create roles for every desk and context combination
# The Harborview rule: an adviser may read portfolios for their own desk,
# and the permitted action differs in and out of market hours. RBAC forces
# one role per combination. With 4 desks and 2 time contexts that is 8 roles
# for this ONE rule; add an approval-limit tier (3 bands) and it is 24.
for desk in desk-a desk-b desk-c desk-d; do
for ctx in markethours afterhours; do
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh create roles \
-r northgate -s name="wealth-adviser-${desk}-${ctx}"
done
done
# Count how many roles now exist that encode this single business rule.
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh get roles -r northgate \
--fields name | jq '[.[] | select(.name | startswith("wealth-adviser-"))] | length'
8 for four desks and two contexts. Note that this is one business rule. Adding a third context, a new desk, or an approval-limit dimension multiplies the count. This multiplicative growth, not the absolute number, is role explosion.
Step 1.2: Set the attributes ABAC will use instead
Set desk and approval_limit user attributes
# Two advisers on different desks. In ABAC these two attributes, plus the
# resource's own desk, express what eight-plus roles tried to encode.
AID=$(docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh get users \
-r northgate -q username=asmith --fields id | jq -r '.[0].id')
JID=$(docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh get users \
-r northgate -q username=jpatel --fields id | jq -r '.[0].id')
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh update users/$AID \
-r northgate -s 'attributes.desk=["desk-a"]' \
-s 'attributes.approval_limit=["500000"]'
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh update users/$JID \
-r northgate -s 'attributes.desk=["desk-b"]' \
-s 'attributes.approval_limit=["100000"]'
Map the desk attribute into the token as a claim
# For a policy to read the desk, it must appear as a token claim. Add a
# user-attribute protocol mapper on a client scope (or the client) so that
# 'desk' is included in the access token.
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh create \
clients/RESOURCE_CLIENT_UUID/protocol-mappers/models -r northgate \
-s name=desk-mapper \
-s protocol=openid-connect \
-s protocolMapper=oidc-usermodel-attribute-mapper \
-s 'config."user.attribute"=desk' \
-s 'config."claim.name"=desk' \
-s 'config."access.token.claim"=true' \
-s 'config."jsonType.label"=String'
desk claim of desk-a. If the claim is absent, confirm the mapper was created on the correct client (the one you enable authorization on in Step 2.1) and that access.token.claim is true.
Phase 2: Rebuild as ABAC with Keycloak Authorization Services
Step 2.1: Enable Authorization Services on a resource-server client
Create the resource-server client
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh create clients \
-r northgate \
-s clientId=portfolio-api \
-s enabled=true \
-s protocol=openid-connect \
-s publicClient=false \
-s serviceAccountsEnabled=true \
-s authorizationServicesEnabled=true \
-s 'redirectUris=["http://localhost:8082/*"]'
# Capture the client UUID (RESOURCE_CLIENT_UUID used elsewhere).
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh get \
clients?clientId=portfolio-api -r northgate --fields id | jq -r '.[0].id'
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh get clients/RESOURCE_CLIENT_UUID -r northgate | jq '.authorizationServicesEnabled' returns true. An Authorization tab now appears for this client in the Admin Console.
Step 2.2: Define the resource, scope and attribute policy
Author the resource and policies (Admin Console)
# In the Admin Console, Clients, portfolio-api, Authorization tab:
#
# 1) Resources: create "portfolio-desk-a"
# Type: urn:portfolio:resource:portfolio
# Scopes: portfolio:read
# Attributes: desk = desk-a (the resource's owning desk)
#
# 2) Scopes: confirm "portfolio:read" exists.
#
# 3) Policies: create a Time policy "market-hours"
# Not Before / Not On or After, or the hour range fields, set to
# represent UK market hours (for example 08:00 to 16:30). The exact
# field layout differs by version; the Console shows the current fields.
#
# 4) Policies: create a Regex policy "desk-matches-desk-a"
# Target claim: desk
# Pattern: ^desk-a$
# This permits when the user's desk claim equals desk-a.
#
# NOTE: a Regex policy compares a claim to a fixed pattern. Comparing
# the SUBJECT's desk to the RESOURCE's desk dynamically (desk == desk)
# is beyond built-in policies; that gap is addressed in Step 2.4 and is
# exactly why Lab 14 introduces an external policy engine.
echo "Author resources and policies in the Authorization tab as described."
Step 2.3: Combine the policies into a permission
Create an aggregate policy and a scope permission (Admin Console)
# Still in the Authorization tab:
#
# 5) Policies: create an Aggregate policy "adviser-may-read-desk-a"
# Apply Policy: desk-matches-desk-a AND market-hours
# Decision strategy: Unanimous (all must permit)
#
# 6) Permissions: create a Scope permission "read-portfolio-desk-a"
# Resource: portfolio-desk-a
# Scope: portfolio:read
# Apply Policy: adviser-may-read-desk-a
# Decision strategy: Unanimous
echo "Combine policies into a unanimous scope permission as described."
Step 2.4: Note the relational gap honestly
desk-a. To avoid a separate permission per desk, you want one rule that says "permit if the subject's desk equals the resource's desk", evaluated dynamically. Keycloak can express this with a JavaScript (script) policy, but script policies are disabled by default and gated behind a preview feature for a good security reason: uploaded scripts run inside the server. This tension, wanting relational attribute logic without running arbitrary code in the identity server, is precisely the architectural pressure that leads to an external, purpose-built policy engine. That is Lab 14.
Phase 3: Evaluate Live Decisions
Step 3.1: Request a decision with the UMA grant for the matching adviser
Obtain a token, then request an RPT for the resource and scope
# Get asmith's access token (direct grant for lab convenience).
ASMITH_TOKEN=$(curl -sk -X POST \
"https://localhost:8443/realms/northgate/protocol/openid-connect/token" \
-d "client_id=portfolio-api" -d "client_secret=REPLACE_WITH_PORTFOLIO_SECRET" \
-d "grant_type=password" -d "username=asmith" \
-d "password=REPLACE_WITH_ASMITH_PASSWORD" -d "scope=openid" | jq -r '.access_token')
# Ask Keycloak for a decision on portfolio:read for the desk-a resource.
curl -sk -X POST \
"https://localhost:8443/realms/northgate/protocol/openid-connect/token" \
-H "Authorization: Bearer $ASMITH_TOKEN" \
-d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
-d "audience=portfolio-api" \
-d "permission=portfolio-desk-a#portfolio:read" \
-d "response_mode=decision" | jq
{"result": true}, a permit for asmith on desk-a. If you receive false while confident the user and time are correct, use the Admin Console Evaluate tool to see which policy denied, then reconcile the live claim (is desk present in the token?) with the policy pattern.
Step 3.2: Request the same decision for the non-matching adviser
Repeat for jpatel on the desk-a resource
JPATEL_TOKEN=$(curl -sk -X POST \
"https://localhost:8443/realms/northgate/protocol/openid-connect/token" \
-d "client_id=portfolio-api" -d "client_secret=REPLACE_WITH_PORTFOLIO_SECRET" \
-d "grant_type=password" -d "username=jpatel" \
-d "password=REPLACE_WITH_JPATEL_PASSWORD" -d "scope=openid" | jq -r '.access_token')
curl -sk -X POST \
"https://localhost:8443/realms/northgate/protocol/openid-connect/token" \
-H "Authorization: Bearer $JPATEL_TOKEN" \
-d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
-d "audience=portfolio-api" \
-d "permission=portfolio-desk-a#portfolio:read" \
-d "response_mode=decision" | jq
{"result": false}. Two advisers, the same requested action on the same resource, and a different decision, entirely because jpatel is on desk-b. No role encoded this; the desk attribute did. This is the ABAC capability that role membership alone cannot provide.
Step 3.3: Show the environment attribute at work
Evaluate outside the time window
# If it is currently outside your configured market-hours window, re-run the
# asmith decision from Step 3.1 and observe it now returns false. If it is
# inside the window, temporarily narrow the market-hours time policy in the
# Admin Console to a range that excludes the current time, re-run, then
# restore the correct window.
#
# Alternatively use the Evaluate tool and set a custom evaluation time.
echo "Re-run the asmith decision outside market hours; expect result: false."
08Testing and Validation
End-to-End Test Scenarios
| Scenario | Steps | Expected Result |
|---|---|---|
| Role explosion count | Create the desk and context roles in Step 1.1 and count them | Eight roles for one rule; count grows multiplicatively with each new dimension |
| Matching adviser permit | Request a decision for asmith on desk-a in market hours | {"result": true} |
| Non-matching adviser deny | Request the same decision for jpatel | {"result": false} |
| Environment deny | Request asmith's decision outside market hours | {"result": false} |
Negative Tests
| Test | Expected Result |
|---|---|
| Remove the desk claim mapper, then request asmith's decision | Deny, because the regex policy cannot match an absent claim; restore the mapper afterwards |
| Request a scope not covered by any permission | Deny by default, demonstrating default-deny behaviour |
| Set the aggregate decision strategy to Affirmative instead of Unanimous and re-test jpatel | Behaviour changes: confirm you understand why, then restore Unanimous, since Affirmative would permit on desk match OR time rather than both |
| Supply a desk claim value not matching the pattern (for example desk-z) | Deny, confirming the regex anchors match exactly |
Common Failure Modes
| Symptom | Likely Cause | Resolution |
|---|---|---|
| Every decision returns false | The desk claim is not in the token, so the regex policy never matches | Confirm the protocol mapper from Step 1.2 is on the portfolio-api client and included in the access token |
| Every decision returns true regardless of desk | The permission or aggregate uses Affirmative logic, or the desk policy is not attached | Set decision strategies to Unanimous and confirm both policies are applied to the permission |
| Time policy never denies | The time window spans the whole day, or the server timezone differs from your expectation | Check the server's timezone and narrow the window; verify with the Evaluate tool using a custom time |
| UMA request returns an error rather than a decision | Wrong resource#scope string, or authorization not enabled on the audience client | Confirm the permission parameter matches the exact resource and scope names, and that portfolio-api has authorization enabled |
09Security Analysis
What Makes This Implementation Secure
- Permissions default to deny, so any resource or scope not explicitly permitted is refused, which fails safe when policy coverage has gaps.
- Attributes that drive decisions are surfaced as verifiable token claims issued by Keycloak, not values the requesting client can assert for itself.
- The compound rule uses unanimous aggregate logic, so a decision requires every condition to be met, not merely one.
- Centralising the decision in a PDP means the rule is defined and governed in one place, rather than duplicated and drifting across application code.
What Is Intentionally Simplified for the Lab
- The desk match is hard-coded per resource rather than compared relationally to the subject's desk; true relational comparison needs script policies or an external engine, which Lab 14 provides.
- The direct grant is used to obtain tokens quickly; production uses the authorization code with PKCE flow from Lab 04.
- TLS certificate verification is disabled against
ib-lbbecause it presents a self-signed certificate. - Resource attributes are set manually in the Console; a production system synchronises resource attributes from the systems that own the data.
Production Hardening Recommendations
| Area | Recommendation |
|---|---|
| Attribute governance | Treat the attributes that drive decisions as security-relevant data: control who can set desk or approval_limit, and audit changes to them as closely as role assignments |
| Policy testing | Build an automated suite of permit and deny cases and run it on every policy change, since an ABAC rule is harder to eyeball than a role name |
| Decision logging | Log each decision with the attributes that drove it, so a permit or deny can be explained after the fact during an audit or incident |
| Hybrid design | Keep roles for coarse, stable grants and reserve attribute policies for the contextual decisions roles handle poorly, rather than converting everything to ABAC |
| Engine selection | When rules require relational attribute logic or portability across many services, evaluate a dedicated policy engine, the architectural decision Lab 14 informs |
10Cleanup
Remove the demonstration roles created to show role explosion
# Delete the combinatorial roles; they existed only to illustrate the problem.
for desk in desk-a desk-b desk-c desk-d; do
for ctx in markethours afterhours; do
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh delete \
roles/wealth-adviser-${desk}-${ctx} -r northgate 2>/dev/null || true
done
done
Optionally remove the portfolio-api client and its authorization config
# Keep portfolio-api if you intend to reuse it as the enforcement target in
# Lab 14 or Lab 16; otherwise remove it and its resources/policies in one go.
docker exec -it ib-keycloak-1 /opt/keycloak/bin/kcadm.sh delete \
clients/RESOURCE_CLIENT_UUID -r northgate
wealth-adviser-* roles are gone with the count query from Step 1.1, which should now return 0. The user attributes on asmith and jpatel are harmless to leave in place and are reused conceptually in Lab 14. The Keycloak cluster is otherwise ready for the next lab.
11Recommended Learning Links
- NIST SP 800-162, Guide to Attribute Based Access Control (ABAC) Definition and Considerations, NIST
- NIST RBAC model, Role-Based Access Controls, Ferraiolo and Kuhn
- Keycloak Authorization Services Guide, Keycloak documentation
- User-Managed Access (UMA) 2.0 Grant for OAuth 2.0, Kantara Initiative
- NIST SP 800-162, the PEP, PDP and PIP decision architecture, NIST
- XACML 3.0 (as a reference ABAC policy language), OASIS
12Portfolio Publishing Guide
Sanitise Before Publishing
This lab handles a client secret and user tokens. Confirm none reach your repository.
Sanitisation checklist commands
grep -R "REPLACE_WITH" . --include="*.sh" --include="*.md" || echo "Clean"
cat >> .gitignore <<'EOF'
*.token
.env
EOF
git status
README for the Repository
README.md skeleton
# IB-SIA-13: RBAC to ABAC
Demonstrates role explosion in a pure RBAC model, then rebuilds the same
access rule as attribute and time based policies using Keycloak
Authorization Services, and evaluates live permit/deny decisions via the
UMA 2.0 grant.
## Stack
Keycloak 24.x (Authorization Services), Python 3.11, curl, jq
## What it demonstrates
- Role explosion: one rule, many combinatorial roles
- ABAC: one policy over subject, resource and environment attributes
- Same role, different decision, driven by attributes and context
- The boundary where built-in policies end and an external engine begins
## Part of the Identity Bytes Senior IAM Architect Track
Lab 13 of 36. See identity-bytes.com for the full curriculum.
Git Commands
Commit and push
git add README.md .gitignore *.sh
git commit -m "IB-SIA-13: RBAC to ABAC with Keycloak Authorization Services"
git push origin main
Track Index Line
Add the following line to your master portfolio index:
IB-SIA-13 | RBAC to ABAC | Intermediate | Role explosion, attribute policies, PEP/PDP/PIP, Keycloak Authorization Services
LinkedIn Draft
The moment you create a role called "adviser-desk-a-markethours-under500k", your access model has already lost.
Role-Based Access Control is clean until the business rules stop being about who someone is and start being about context: which desk owns this, what time is it, how much is at stake. Encode those with roles and you get role explosion, one new role for every combination, growing multiplicatively until no reviewer can honestly recertify what they are approving.
This week I took one real rule ("an adviser may read a portfolio for their own desk, during market hours") and built it both ways. In RBAC it became eight roles for four desks and two time windows, and that was before adding approval limits. In ABAC it became a single policy over three attributes: the user's desk, the resource's desk, and the clock.
The demonstration that makes it click: two advisers with the identical job title ask to read the same portfolio. One is permitted, one is denied, purely because of their desk attribute. Then the permitted one is denied an hour later, purely because the market closed. No role can express that. Attributes and context can.
I also drew the honest boundary. Built-in policy engines handle a lot of ABAC, but comparing one attribute to another at decision time is where they stop and a dedicated policy engine begins. That is the next thing I am building.
How many of your roles are really a context rule wearing a role's clothing?