What Is a JavaScript/Rules-Based Policy in Keycloak?
A complete, no-code walkthrough of Keycloak's authorization policies — what a policy actually decides, how the rules-based and scripted engines work internally, and how to use them safely.
Picture an office building with a smart access badge system. A basic badge system might only ask one question at the door: “does this badge exist?” A smarter system asks a small bundle of questions at once — is it a weekday, is it before 8 PM, does this badge belong to someone in the Finance department, and is this door even one Finance staff are allowed through? Keycloak’s rules-based and JavaScript policies are exactly that smarter bundle of questions, evaluated automatically every time someone tries to access a protected resource. This post unpacks what these policies are, how Keycloak evaluates them internally, and how they fit into the bigger authorization picture.
1Core Concepts
Before policies make sense, it helps to separate two things Keycloak deliberately keeps apart: authentication and authorization.
Authentication versus authorization
Authentication answers “who are you?” — that’s the login step, handled by Keycloak’s identity side. Authorization answers a completely different question: “now that I know who you are, what are you actually allowed to do?” Keycloak’s authorization services, including policies, live entirely on this second side of the fence.
Authentication is showing your ID at the front desk of an office building. Authorization is the separate rulebook that decides which floors your ID badge will actually open the elevator to. You can be a perfectly verified, real employee, and still be denied entry to the executive floor — that denial is authorization, not authentication.
What a “policy” actually is
In Keycloak’s authorization model, a policy is a single, reusable rule that produces one of two outcomes: permit or deny. Policies don’t directly protect anything by themselves — they get attached to permissions, which in turn protect specific resources or scopes. Keycloak ships several policy types (role-based, user-based, time-based, client-based, and more), and two of the most flexible are the rules-based policy and the JavaScript policy.
Policy
A single rule that evaluates to permit or deny, based on some condition.
Permission
A binding that says “this resource/scope requires these policies to agree before access is granted.”
Resource
Something being protected — an API endpoint, a document, a UI section.
Resource Server
The client application registered in Keycloak whose resources are being protected by policies.
Rules-Based Policy
A policy whose logic is expressed through a structured, condition-and-outcome format rather than free-form code.
JavaScript Policy
A policy whose decision logic is written as custom JavaScript, evaluated inside Keycloak’s policy engine.
Built-in policy types like role-based or time-based cover common cases cleanly, but real organizations often need logic that doesn’t fit a single dropdown — “allow access only if the user’s department matches the resource’s owning department, and it’s during business hours.” Rules-based and JavaScript policies exist precisely to express that kind of custom, multi-condition decision.
2Architecture and Components
Policies don’t act alone — they’re one layer inside Keycloak’s broader authorization services architecture.
The authorization services stack
Every resource server registered with Keycloak carries its own authorization configuration: a catalog of resources, a catalog of scopes (fine-grained actions like “read” or “approve”), a catalog of policies, and a catalog of permissions that tie the previous three together. Rules-based and JavaScript policies are entries in that policy catalog, sitting alongside simpler policy types.
Resource
The protected “thing” — e.g. an invoice record or an admin dashboard.
Scope
A named action on a resource, such as “view” or “delete.”
Policy Provider
The internal Keycloak module that knows how to evaluate one specific policy type.
Permission
Links one or more resources/scopes to one or more policies, and defines the aggregation logic.
Policy Enforcement Point
Code inside or in front of the resource server that actually asks Keycloak for a decision before serving a request.
Evaluation Context
The bundle of information (user attributes, roles, request details) handed to a policy at decision time.
Where the rules-based engine sits
A rules-based policy is powered by a lightweight internal rules engine. Instead of writing free-form code, an administrator defines conditions using a structured format — comparisons, attribute checks, group membership checks — and the rules engine walks through those conditions the same way every time, in a predictable, auditable order.
Where the JavaScript engine sits
A JavaScript policy, by contrast, hands evaluation to an embedded JavaScript runtime inside Keycloak. The script receives the same evaluation context object, and instead of filling in a form of conditions, the administrator writes logic directly, deciding the outcome by explicitly granting or denying within the script.
Why this matters for governance
Because rules-based policies use a constrained, structured format, they tend to be easier to review, diff, and audit. JavaScript policies, being free-form code, offer more power but require more careful code review, since arbitrary logic — and arbitrary mistakes — become possible.
3Internal Working
What actually happens, step by step, when Keycloak evaluates one of these policies?
Building the evaluation context
Before any policy logic runs, Keycloak assembles a context object describing the current request: who the user is, which roles and groups they belong to, any custom attributes on their account, details about the resource being requested, and sometimes contextual information like the time of the request or claims from the incoming token.
How the rules-based engine evaluates conditions
For a rules-based policy, Keycloak walks through each configured condition in order — for example, “user must have attribute department equal to Finance” and “current time must be within business hours” — and combines their individual true/false results according to the policy’s configured logic (commonly, all conditions must pass). The final combined result becomes the policy’s permit-or-deny outcome.
The rules-based engine works like a paper checklist pinned to a clipboard. Each line item is a yes/no question. The inspector ticks every box in order, and only signs off with “approved” if every required box is ticked yes.
How the JavaScript engine evaluates a script
For a JavaScript policy, Keycloak instead loads the administrator-provided script into its embedded JavaScript engine and executes it, passing in the same evaluation context. The script inspects whatever properties it needs from that context and calls a decision function to explicitly grant or deny access. Because it’s genuine code, it can express branching logic, loops, or calculations that a purely structured rules list cannot.
A JavaScript policy is not “smarter” or “more secure” than a rules-based one by default — it’s simply more flexible. Flexibility without discipline can just as easily introduce subtle authorization bugs, since arbitrary code is harder to statically verify than a structured checklist.
Combining multiple policies under one permission
A single permission in Keycloak can require several policies to weigh in together — for example, a role-based policy and a rules-based policy attached to the same permission. Keycloak supports different aggregation strategies (all must permit, or at least one must permit), letting administrators compose simple and custom policies into a single, coherent decision.
4Data Flow and Lifecycle
Tracing a single access request from the client all the way to a permit/deny decision.
sequenceDiagram
participant Client as Client Application
participant PEP as Policy Enforcement Point
participant KC as Keycloak (Authorization Services)
participant Engine as Rules/JS Policy Engine
Client->>PEP: Request access to protected resource
PEP->>KC: Ask for authorization decision (resource + scope)
KC->>KC: Build evaluation context (user, roles, attributes, time)
KC->>Engine: Evaluate attached policies
Engine-->>KC: Return permit or deny per policy
KC->>KC: Combine policy results per permission logic
KC-->>PEP: Final decision (permit/deny)
PEP-->>Client: Serve resource or return access denied
The lifecycle of a policy definition
Beyond a single request, a policy itself has a lifecycle as an administered object inside Keycloak, from creation to eventual retirement.
Defined
An administrator creates the policy — filling in conditions for rules-based, or providing a script for JavaScript-based.
Attached
The policy is linked to one or more permissions, which in turn cover specific resources and scopes.
Evaluated
Every relevant access request triggers evaluation, live, against the current state of the user and context.
Revised
Business rules change, so conditions or script logic are updated — taking effect on the very next evaluation.
Retired
The policy is detached from permissions and removed once the business rule it encoded no longer applies.
Unlike a signed token’s contents, authorization decisions are evaluated fresh against current user attributes and context each time. This means a change to a user’s department or group membership takes effect on the very next access attempt, without needing a new login.
5Advantages, Disadvantages, and Trade-offs
Choosing between built-in, rules-based, and JavaScript policies comes down to matching flexibility to complexity.
Advantages
- Expresses authorization logic that simple role checks cannot capture
- Rules-based format stays structured, readable, and easy to audit
- JavaScript policies allow essentially arbitrary custom logic when genuinely needed
- Policies are centrally managed in Keycloak, not scattered across application code
- Multiple policy types can be combined under one permission
Disadvantages / Trade-offs
- JavaScript policies require careful code review, since they run arbitrary logic
- Complex policy chains can become hard to reason about as they grow
- Live evaluation adds a small amount of latency compared to static role checks
- Debugging a denied decision across several combined policies can be non-trivial
6Security
Custom authorization logic is a high-value target, so it deserves proportionate care.
Treat policy scripts like production code
A JavaScript policy runs with access to sensitive evaluation context — user attributes, roles, and resource details. It should go through the same review discipline as any other piece of production authorization code: version-controlled, peer-reviewed, and tested against both allow and deny scenarios before deployment.
Writing a policy that only checks for the conditions that should grant access, without an explicit, deliberate default-deny fallback for anything unexpected. Authorization logic should fail closed — denying access when a condition is ambiguous or an expected attribute is missing — rather than accidentally failing open.
Least-privilege attribute exposure
Only expose the user attributes and context fields that a policy genuinely needs to make its decision. Minimizing what’s available in the evaluation context reduces the chance that a poorly written script accidentally leaks or misuses sensitive data during evaluation.
Fail Closed
Default to deny when a condition can’t be conclusively evaluated.
Code Review
Treat JavaScript policies as production code requiring peer review.
Least Context
Only expose attributes genuinely required for the decision.
Test Both Paths
Verify policies against scenarios that should permit and scenarios that should deny.
7Monitoring, Logging, and Metrics
Authorization decisions that fail silently are among the hardest incidents to diagnose after the fact.
What’s worth watching
Operationally, it’s valuable to track the rate of denied decisions per permission (a sudden spike often signals a misconfigured attribute or a broken upstream data source), the evaluation latency of individual policies (a JavaScript policy doing something expensive can slow down every protected request), and any runtime exceptions thrown inside scripted policies, which should always be treated as incidents rather than ignored.
Auditing decisions, not just outcomes
For sensitive resources, logging which specific policy caused a deny — not just “access denied” — makes post-incident review and compliance audits dramatically easier.
8Design Patterns and Anti-patterns
Some habits keep authorization logic maintainable; others quietly accumulate risk.
Problem
A team reaches for a JavaScript policy for every custom rule, even ones that could be expressed as simple structured conditions.
Why It’s Harmful
Free-form scripts are harder to review, harder to diff during audits, and more likely to contain subtle logic bugs than an equivalent structured rule.
Correct Approach
Default to a rules-based policy whenever the logic fits its structured condition model; reserve JavaScript policies for genuinely complex, code-shaped decisions.
Problem
Dozens of overlapping policies are attached to the same permission without documentation of what each one is meant to guard against.
Why It’s Harmful
Over time, nobody can confidently predict the outcome of a request, and removing an “unused-looking” policy risks silently opening a hole.
Correct Approach
Keep each policy scoped to one clear intent, document its purpose, and periodically review which policies are actually still relevant.
A healthier pattern: layered policies
A resilient design separates broad, coarse-grained policies (like a role check) from narrow, fine-grained ones (like a rules-based attribute check), combining them under a permission rather than cramming every condition into a single sprawling script.
9Best Practices and Common Mistakes
A short, practical checklist for teams building custom authorization logic in Keycloak.
Start structured
Reach for rules-based conditions before writing custom script logic.
Name policies clearly
Use descriptive names so the intent of each policy is obvious months later.
Keep scripts small
A JavaScript policy should decide one thing clearly, not sprawl into a mini application.
Version and review
Store policy scripts in version control and require review before changes go live.
Assuming a policy that “worked in testing” will behave identically in production, without accounting for real user attributes potentially being missing, malformed, or inconsistently populated across accounts.
10Real-World and Industry Examples
Custom authorization policies show up wherever access decisions are more nuanced than a simple role check.
Time-and-location restricted access
Financial and healthcare systems often use rules-based policies to restrict sensitive actions to business hours, or to specific approved network locations, layered on top of ordinary role checks.
Attribute-based multi-tenant access
SaaS platforms serving multiple customer organizations often use custom policies to ensure a user from one tenant can never access another tenant’s resources, even if role names happen to overlap.
Dynamic approval workflows
Enterprises with document-approval systems sometimes use JavaScript policies to encode “a manager can approve requests from their own direct reports only,” logic too relational for a simple built-in policy type to express.
11Frequently Asked Questions
No. A role-based policy checks a fixed condition — role membership. A JavaScript policy can check anything expressible in code, including but not limited to roles.
Usually not. A large share of custom authorization needs can be expressed through a rules-based policy’s structured conditions, which are easier to maintain and audit.
Yes. A permission can require several policies together, and Keycloak supports different combination strategies for how their individual results are merged.
No. Policy evaluation happens live against current context, so changes to a user’s attributes or group membership are reflected immediately on the next request.
This should be treated as an operational incident. A robust deployment monitors for script exceptions and ensures the system fails closed — denying access — rather than silently permitting it.
No. Role-based policies remain a simple, efficient first layer for many use cases; rules-based and JavaScript policies complement them for the cases that need finer-grained logic.
12Summary and Key Takeaways
Rules-based and JavaScript policies give Keycloak’s authorization services the power to make access decisions that go far beyond a simple role check — combining user attributes, context, and custom logic into a single permit-or-deny outcome. Rules-based policies favor structure and auditability; JavaScript policies favor raw flexibility, at the cost of needing the same discipline as any other production code. Used well, layered together, and monitored carefully, they let an organization encode genuinely nuanced access rules without scattering that logic across application code.
Key Takeaways
- Policies decide, permissions protect — a policy alone doesn’t guard anything until attached to a permission.
- Rules-based means structured conditions — predictable, auditable, and usually the right first choice.
- JavaScript means custom code — powerful, but requiring the same review discipline as production software.
- Evaluation is live, not cached — attribute changes take effect on the very next request.
- Policies can be combined — layering coarse and fine-grained policies under one permission is a healthy pattern.
- Fail closed, always — ambiguous or missing context should lead to deny, never accidental permit.
- Monitor deny rates and script errors — silent authorization failures are among the hardest incidents to trace later.