- Request:
- a user holding the realm role
support-agentreads ticket 4471. - Input:
- the roles in the user's Keycloak token.
- Answer:
- allow.
The same user deletes ticket 4471. No role grants delete.
- Answer:
- deny.
In Keymate, Keycloak fine-grained authorization decides each request from who is asking, what they are touching and the conditions at that moment, not from the roles in a token alone. This is also called fine-grained access control, and it works through policies kept outside your application code. Your Keycloak users, roles and groups stay where they are and become policy inputs.
Roles work until a rule depends on the record someone is touching. "Approvers can approve invoices" is a role. "Approvers can approve invoices of their own department, below 50,000, during business hours in the invoice's region" is four conditions. Roles can only express them by multiplying: one role per department, per limit, per region. Our post on the limits of RBAC explains why role counts grow with every exception.
The signs are easy to recognize in a Keycloak realm:
invoice-approver-emea-under-50k.if statements repeated in several services.Coarse-grained authorization answers "may this user use this application?". Fine-grained authorization answers "may this user approve this invoice, now?". Keycloak's own Authorization Services answers part of that on the server, and a policy enforcer library applies the result inside each application. Keymate keeps Authorization Services available and on top of it decides every request in the request path, so applications in any language are covered.
Each policy uses the model that fits its rule, and one policy can combine several.
| Model | Decides by | Example |
|---|---|---|
| Role-based (RBAC) | The roles a user holds | Support agents can read tickets |
| Attribute-based (ABAC) | Attributes of the user, the resource and the moment | Doctors can open records of patients in their own ward |
| Policy-based (PBAC) | Named policies that combine conditions | Payments above 10,000 need a second approver's role |
| Relationship-based (ReBAC) | How the user is related to the resource | Members of a project team can edit the project's documents |
| Risk-adaptive (RADAC) | Risk signals at the time of the request | A login from a new device can read but not export |
| Data-scoped (DSAC) | Which records inside a resource the user may see | An account manager can open only the customers in their region |
Role-based access control (RBAC). Role-based access control grants permissions to roles and roles to users.
support-agent reads ticket 4471.The same user deletes ticket 4471. No role grants delete.
Existing Keycloak realm and client roles are read as they are; nothing is renamed or migrated.
Attribute-based access control (ABAC). Attribute-based access control decides from attributes of the user (ward, department, clearance), the resource (owner, classification, amount) and the moment (time, network, channel).
user.ward = cardiology from the Keycloak user profile, resource.ward = cardiology from the record, the time is 14:20.The same doctor opens a record with resource.ward = oncology.
One ABAC rule replaces a role per ward. ABAC on Keycloak starts from the user profile attributes you already keep. A policy can add a time condition, such as opening hours, without a new role. It can also require a role and an attribute together: the support-agent role and the same ward as the record.
Policy-based access control (PBAC). Policy-based access control writes each rule as a named policy that combines conditions from the other models. It also states how several policies combine into one answer: every policy must allow, or one allow is enough.
payments-release covers payments above 10,000: it needs an approval limit of at least 10,000 or the senior-approver role.The same payment, released by a payment approver who also holds the senior-approver role.
Relationship-based access control (ReBAC). Relationship-based access control decides from how the user is connected to the resource: member of a team, owner of a project, parent organization of a customer.
checkout-spec belongs to project Checkout.checkout-spec. The decision point follows the chain.Ana leaves team Payments. Her next edit is denied, and nobody touched the document's permissions.
Keymate stores relationships in OpenFGA, the open-source relationship-based authorization engine, so Keycloak ReBAC uses OpenFGA's model of relationships.
Risk-adaptive access control (RADAC). Risk-adaptive access control adds the risk of the request to the decision. A risk scorer turns signals such as a new device, an unusual location, the time of day or recent failed sign-ins into a score. You can replace the scorer with your own.
The user completes step-up authentication, and the policy reads the result on the next request. The same export: allow.
Data-scoped access control (DSAC). Data-scoped access control decides whether a user may reach a given record, using attributes of that record.
user.region = north, customer.region = north.The same manager opens a customer in region South.
The payment release above reads two roles (payment-approver and senior-approver) and an attribute (the user's approval limit), combined in one named policy and, if you add it, a risk score, all in one decision.
An access control list (ACL) attaches to each object a list of who may do what with it, as file systems, databases and network devices do. ACLs are precise, but because they live on each object, the rule behind them ("the team that owns the project can edit it") is written nowhere. Discretionary access control (DAC) lets an object's owner change its list. Mandatory access control (MAC) sets access by labels, such as classification levels, that users cannot change. Keymate policies write the rule once: relationships do what per-object lists do, and attributes do what labels do.
A Keymate policy names the resource and the action it covers, the conditions that must hold, the effect (allow or deny) and how it combines with other policies on the same resource. Policies are written in the admin console.
A visual editor for people who think in conditions, and a policy language for people who prefer code. Both produce the same policy, and you can switch between the two views of one policy.
A validator checks each policy before anyone can activate it.
A new policy can start from a template.
Every change has an author, a reason and a diff. You can require an approver before any policy change reaches production. Authorization Governance covers that change cycle.
The payment release above, in the policy language:
hasEffectiveRole("payment-approver")
&& (attributes.approval_limit >= 10000 || hasEffectiveRole("senior-approver"))A payment approver can release it if their approval limit is 10,000 or more, or if they also hold the senior-approver role.
For each allow or deny, Keymate records which policy decided and which condition was met or missed, and states what would change the outcome. For the payment above, the explanation names the policy (payments-release), the condition that was missed (an approval limit below 10,000 and no senior-approver role) and what would allow the request (a payment approver with a limit of 10,000 or more, or with the senior-approver role).
When a user asks why they were denied, support reads that explanation. Every decision is also recorded with the policy version and any approval behind it. That is the evidence auditors ask for, and Access evidence for audits shows what the record contains.
Keymate enforces the same policies at five points: the Access Gateway in the request path, your API gateway, the service mesh between services, SDKs inside an application, and event streams. For event streams, Keymate decides which application may publish to, or read from, which stream. The decision is made on every request, not once at login: if a right is revoked at 10:02, the request at 10:03 is denied. Which enforcement point fits which application, and what each one needs from your code, is covered in the enforcement deep dive.
A service or an AI agent that calls your APIs signs in as a software client, and the same policies decide its requests.
A service signs in with the OAuth client credentials grant. A policy can say that billing-worker may create invoices only in its own tenant and never delete them. That rule is checked on every call.
Today an AI agent that calls your APIs signs in as a service, and each of its calls is decided by the same policies as any other client, outside the agent, so an instruction the agent receives cannot widen what the policy allows. Treating an agent as an identity of its own, evaluated together with the person it acts for, is on our Roadmap.
Our post why AI agents need fine-grained authorization explains why authentication alone leaves agents over-privileged.
Use Keycloak's built-in Authorization Services, or add a decision point such as Keymate's. Authorization Services evaluates resources, scopes and policies on the server, and applications apply the result with a policy enforcer library. Keymate reads your Keycloak roles, groups, organizations and user attributes as they are and combines them with relationships and risk signals in one policy set. It enforces each decision at the Access Gateway, in your API gateway, in the service mesh or through SDKs, so applications in any language are covered.
Keycloak Authorization Services fits rules built from roles, groups, time and resource ownership in Java applications, with nothing beyond Keycloak to run. OpenFGA, an open-source relationship-based authorization engine and a Cloud Native Computing Foundation (CNCF) project, fits access that follows relationships across many objects (sharing, team membership, nested folders). You do not have to choose: Keymate keeps Authorization Services available and stores its relationship-based model in OpenFGA, under one set of policies and the same enforcement.
A good policy decision point for Keycloak reads Keycloak's tokens, roles, groups and attributes without a synchronization job, decides every request rather than once at login, enforces the answer in the request path and records each decision with the policy version behind it. Use these checks when you compare. Keycloak's Authorization Services decides per request, for Java applications. Keymate meets all of them: its decision point is a Keycloak extension inside Keymate's Keycloak build, and a different decision engine can be plugged in instead.
RBAC decides from the roles a user holds: support agents can read tickets. ABAC decides from attributes of the user, the resource and the moment: doctors can open records of patients in their own ward. ReBAC decides from how the user is connected to the resource: members of a project team can edit the project's documents. In Keymate one policy can combine all three, for example a role, a ward attribute and a team membership in the same rule.
Take the decision out of the agent: every action the agent takes is checked by a policy engine it cannot change. The policy evaluates the agent's identity and the identity of the person it acts for together, along with tenant, channel and risk, and decides per request what data the agent may reach. In Keymate an agent is decided like any other client today; treating an agent as an identity of its own, evaluated together with the person it acts for, is on our Roadmap.
Collect the signals at request time (device, location, time, recent failed sign-ins), turn them into a risk score, and write policies that read the score next to roles and attributes: read allowed at any score, export only below a threshold or after step-up authentication. Keymate's risk-adaptive model does this per request, with a risk scorer you can replace with your own.
Yes, when it decides each API call separately instead of trusting a token for its whole lifetime. Keymate does this. Services sign in with the OAuth client credentials grant, and every API call they make is decided against the same policies as a person's: which service may call which endpoint, for which tenant, with which action.