Resources, Scopes, Permissions & Policies in Keycloak — A Practical Guide for Mapping Legacy RBAC
You’ve got ~250 “capabilities” today (e.g., viewAccount, viewTransaction) granted via user–group membership and the occasional direct…
Resources, Scopes, Permissions & Policies in Keycloak — A Practical Guide for Mapping Legacy RBAC

You’ve got ~250 “capabilities” today (e.g., viewAccount, viewTransaction) granted via user–group membership and the occasional direct grant. You want to migrate this to Keycloak’s Authorization Services without drowning in a sea of objects. Good news: you can keep your mental model and end up with something cleaner, more scalable, and easier to reason about.
Below is a field-tested way to translate legacy RBAC into Keycloak’s resources/scopes/permissions/policies, plus a naming blueprint and migration checklist you can apply immediately.
Keycloak for Developer : https://tobiweissmann.gumroad.com/l/dixdgp
Summary (answers first)
Scopes: Prefer verb-style scopes reused across a resource type (e.g.,
view,create,update,delete) not “baked” scopes likeviewAccount. Attach the same scope name to different resource types (e.g.,account,transaction). This keeps scope names short and consistent while still being precise.
Permissions: Create scope-based permissions for operations (e.g., “View Account”) and resource-based permissions for ownership/location constraints. One permission per meaningful rule is fine; don’t try to encode the entire matrix into one mega-permission.
Conflicts / multiples: Final access is decided by Decision Strategy. If more than one permission applies, Affirmative means any “permit” wins; Unanimous means all relevant permissions must permit.
Groups & roles: You don’t need one policy per group. Use a Group-based or Role-based policy that lists multiple groups/roles. You can also layer Aggregated policies to compose rules.
Outcome: Your old viewAccount capability becomes:
a resource type
account(+ many resource instances),
a
viewscope attached to the resource type,
a scope-based permission “View Account,”
with a policy referencing your “Help Desk” role/group (or any others). It’s conceptually the same, but now consistent and extensible.
A Clean Mental Model
Think of Keycloak Authorization like this:
Resource = “What” you guard (e.g., an
account, a specificaccount/{id}, atransaction).
Scope = “What action/operation” on that resource (e.g.,
view,export,approve).
Policy = “Who or under what conditions” (e.g., “Help Desk role,” “Group = Finance,” “User attribute region=EU,” “Time of day,” “Owner is caller”).
Permission = “Glue” that applies one or more policies to a resource and/or scope (e.g., “View Account” permission = policy(Help Desk OR Account Owner) → scope(
view) on resource typeaccount).
This separation keeps your system maintainable as requirements evolve.
Scopes: Shared Verbs, Specific Resource Types
Your question: “Should I create one global view scope, or viewAccount, viewTransaction, etc.?”
Recommendation: Use shared verb scopes (e.g., view) and bind them to specific resource types (e.g., account, transaction). In practice:
Define resource types:
account,transaction,customer, etc.
For each type, attach the verbs you need:
view,create,update,delete, plus any domain-specific verbs (export,approve,reconcile, …).
Avoid baking the resource name into the scope (e.g.,
viewAccount). You’ll end up duplicating the same idea and exploding your scope count.
Why this works well
Consistency: Everyone knows what
viewmeans across the board.
Composability: You can express rules like “to view transactions you must also be allowed to view their parent account.”
Refactoring-friendly: Adding a new resource type rarely forces a scope explosion.
If you absolutely must disambiguate, prefer a namespaced style like
account:viewandtransaction:view. But conceptually it’s still a verb reused across types.
Permissions: How Many, and Which Kind?
Keycloak gives you two key flavors:
Scope-based permission: Evaluate policies in the context of one or more scopes (optionally constrained to a resource or resource type).
Resource-based permission: Evaluate policies in the context of one or more resources (optionally with their scopes).
Rule of thumb
Use scope-based permissions to model operations (“Can X do
viewonaccount?”).
Use resource-based permissions to capture object-level conditions (e.g., “User is the owner of
account/{id},” “Account region = user.region,” “Resource attributesensitivity=low,” etc.).
This separation keeps policy logic small and testable.
Do I need a permission for every resource/scope pair?
Not literally one per instance. Typically you’ll create permissions at the resource type level (e.g., a single “View Account” permission that applies to all account resources) and optionally add instance-level constraints via attributes/policies when needed.
What if Multiple Permissions Apply?
Keycloak evaluates all applicable permissions and policies, then combines results using Decision Strategy:
Affirmative (at least one permission says “permit” → allow)
Unanimous (all relevant permissions must say “permit” → allow)
Inside each permission, you can also choose its Decision Strategy for combining multiple policies. And any policy can be marked Negative (logical NOT) to invert its result.
Practical tip:
On your resource server (the client’s Authorization → Settings), start with Unanimous for safety.
Only switch to Affirmative if you intend “any green light is enough.”
Mapping Your Legacy Model
Today
A User
Is in one or more Groups
Groups (and sometimes users directly) have Capabilities like
viewAccount,viewTransaction
Tomorrow (Keycloak)
Resources
Resource type:
account→ instances likeaccount/{id}
Resource type:
transaction→ instances liketransaction/{id}
Scopes
For
account:view,update,export…
For
transaction:view,refund,void…
Policies
Role-based (“Help Desk”, “Account Owner”, “Ops Manager”)
Group-based (“Group = Finance”, “Group = Tier-2 Support”)
Attribute-based (“user.region = resource.region”)
Time-based / script-based as needed Use Aggregated policies to OR/AND these together cleanly.
Permissions
Scope-based: “View Account” → applies to scope
viewon resource typeaccount; uses policyHelp Desk OR Account Owner.
Scope-based: “View Transaction” → scope
viewon resource typetransaction; policyHelp Desk OR Account Owner OR Auditor.
Resource-based: “Owns Account” → resource
account(any), policyuser.id == resource.ownerId(script/attributes).
Combine with Unanimous at the server level if you require both the operation right and the ownership right.
Why this mirrors your old system nicely
Your viewAccount capability becomes a permission (“View Account”), enforced via a policy (“Help Desk membership” etc.) on a well-defined resource type (account) and scope (view). It reads like plain English and scales with you.
Do I Need a Policy per Legacy Group?
No. Use one Group-based policy that lists many groups, or a few policies grouped via an Aggregated policy. The same goes for roles. This avoids policy sprawl.
Example
Policy:
Policy.HelpDesk(Group-based:helpdesk)
Policy:
Policy.AccountOwner(Role-based:account_owner)
Aggregated policy:
Policy.CanViewAccount = HelpDesk OR AccountOwner
Attach Policy.CanViewAccount to your “View Account” permission.
Worked Example: Accounts & Transactions
Let’s model two capabilities you mentioned:
Resources & Scopes
Resource type:
accountScopes:view,update
Resource type:
transactionScopes:view,refund
Policies
Role.HelpDesk(Role-based:helpdesk)
Role.AccountOwner(Role-based:account_owner)
Attr.SameRegion(Script/Attribute-based:user.region == resource.region)
Aggregated:
CanViewAccount = Role.HelpDesk OR Role.AccountOwner
CanViewTransaction = Role.HelpDesk OR Role.AccountOwner
Permissions
Scope-based:
Perm.ViewAccount
Scopes:
view(on resource typeaccount)
Apply Policy:
CanViewAccount AND Attr.SameRegion
Decision Strategy:
Unanimous
Scope-based:
Perm.ViewTransaction
Scopes:
view(on resource typetransaction)
Apply Policy:
CanViewTransaction AND Attr.SameRegion
Decision Strategy:
Unanimous
Optional cross-dependency: require account visibility before transaction visibility
Add an Aggregated policy
HasAccountVisibilitythat internally evaluatesPerm.ViewAccount(or reproduces the same conditions).
Then set
Perm.ViewTransactionto requireHasAccountVisibilityandCanViewTransaction.
Result
Help Desk and Account Owners can view accounts and transactions in their region.
Changing the condition (e.g., region) is a policy tweak, not a redesign.
Adding
exportlater is simply adding a scope + permission, reusing policies.
Naming & Modeling Conventions (copy/paste ready)
Resource Types
Singular domain nouns:
account,transaction,customer
Instances identified via URI or ID attribute:
account/{id}.
Scopes (verbs)
CRUD-y:
view,create,update,delete
Domain:
export,approve,refund,transfer.
Optional namespacing:
account:viewif you need to disambiguate externally.
Policies
Role.<name>: role-based (client roles for app-specific, realm roles for cross-app)
Group.<name>: group-based
Attr.<rule>: attribute/script-based (e.g.,Attr.SameRegion)
Agg.<business_meaning>: aggregated (“OR of these”, “AND of those”)
Permissions
Perm.<Verb><ResourceType>for scope-based (e.g.,Perm.ViewAccount)
Perm.<Rule><ResourceType>for resource-based constraints (e.g.,Perm.OwnsAccount)
Decision Strategy
Start Unanimous on the resource server and most permissions.
Use Affirmative only when any single green light should allow access.
Migration Playbook for 250 Capabilities
Inventory & Normalize
Turn every legacy capability into
{resource_type, verb}.viewAccount→{account, view};viewTransaction→{transaction, view}.
De-duplicate Verbs
Aim for a small, reusable verb set per resource type.
Define Resource Types & Instances
Create Keycloak resource types for your nouns; add attributes you’ll need for policy (owner, region, org, sensitivity).
Create Scopes
Attach your verbs to each resource type.
Model Policies
Translate legacy groups/roles into Role- or Group-based policies.
Add attribute policies for ownership, region, etc.
Compose with Aggregated policies.
Create Permissions
One scope-based permission per
{resource_type, verb}you actually grant in real life.
Add resource-based permissions for object-level rules (ownership, region).
Set Decision Strategy
Use Unanimous globally; override per-permission if necessary.
Test with Evaluate
In the client’s Authorization → Evaluate, simulate users, roles, groups, and resources to confirm outcomes.
Iterate Safely
Roll out per module; keep logs; add deny-by-default fallbacks.
Common Pitfalls & How to Avoid Them
Scope explosion: Don’t encode the resource in scope names (
viewAccount). Reuse verbs; use resource types to scope meaning.
Permission soup: One permission per meaningful rule is enough. Use Aggregated policies to compose, not copy-paste.
Overusing direct user grants: Prefer roles/groups + policies. Keep identity hygiene clean.
Accidental Affirms: If you meant “must be owner and have the role,” Unanimous is your friend.
Forgetting attributes: Many real rules are attribute-driven (owner, region, cost center). Model those on the resource and user.
Mini Blueprint (your viewAccount case)
Resource type:
account
Scope:
view
Policy:
Role.HelpDesk(orGroup.HelpDesk) and/orRole.AccountOwner
Permission (scope-based):
Perm.ViewAccount
Scopes:
viewonaccount
Apply Policy:
Role.HelpDesk OR Role.AccountOwner
Decision Strategy:
Unanimous(with any additional constraints likeAttr.SameRegion)
That’s it — clean, readable, scalable.
Final Thought
Your old world of “capabilities by group” maps 1:1 to scope-based permissions by policy in Keycloak. The biggest win you’ll get from this migration isn’t just parity — it’s clarity: consistent verbs, explicit resource types, and modular policies that you can combine like LEGO. Once you adopt that shape, adding the 251st capability is a five-minute change, not a re-design.
Keycloak for Developer : https://tobiweissmann.gumroad.com/l/dixdgp
메타데이터
- post_id
- fd5a60f392bf
- slug
- resources-scopes-permissions-policies-in-keycloak-a-practical-guide-for-mapping-legacy-rbac-fd5a60f392bf
- url
- https://medium.com/@trivajay259/resources-scopes-permissions-policies-in-keycloak-a-practical-guide-for-mapping-legacy-rbac-fd5a60f392bf
- canonical_url
- https://medium.com/@trivajay259/resources-scopes-permissions-policies-in-keycloak-a-practical-guide-for-mapping-legacy-rbac-fd5a60f392bf
- author_url
- https://medium.com/@trivajay259
- status
- ok
- fetched_at
- 2026-08-24 02:07:13