Keymate

Keycloak fine-grained authorization: how it works

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.

When roles stop scaling

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:

  • Role names carry customer, region or limit: invoice-approver-emea-under-50k.
  • Ownership checks ("is this user on the project that owns this document?") are if statements repeated in several services.
  • Temporary access is a ticket to add a role and a reminder to remove it.

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.

Fine-grained authorization models, with one worked example each

Each policy uses the model that fits its rule, and one policy can combine several.

ModelDecides byExample
Role-based (RBAC)The roles a user holdsSupport agents can read tickets
Attribute-based (ABAC)Attributes of the user, the resource and the momentDoctors can open records of patients in their own ward
Policy-based (PBAC)Named policies that combine conditionsPayments above 10,000 need a second approver's role
Relationship-based (ReBAC)How the user is related to the resourceMembers of a project team can edit the project's documents
Risk-adaptive (RADAC)Risk signals at the time of the requestA login from a new device can read but not export
Data-scoped (DSAC)Which records inside a resource the user may seeAn 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.

Request:
a user holding the realm role support-agent reads 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.

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).

Request:
Dr. Aydın opens patient record 2093.
Inputs:
user.ward = cardiology from the Keycloak user profile, resource.ward = cardiology from the record, the time is 14:20.
Answer:
allow.

The same doctor opens a record with resource.ward = oncology.

Answer:
deny.

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.

Request:
a user with the payment-approver role and an approval limit of 5,000 releases a payment of 12,500. Policy payments-release covers payments above 10,000: it needs an approval limit of at least 10,000 or the senior-approver role.
Answer:
deny.

The same payment, released by a payment approver who also holds the senior-approver role.

Answer:
allow.

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.

Relationships:
Ana is a member of team Payments; team Payments owns project Checkout; document checkout-spec belongs to project Checkout.
Request:
Ana edits checkout-spec. The decision point follows the chain.
Answer:
allow.

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.

Request:
a user exports the quarterly report from a device first seen today.
Risk score:
high.
Answer:
deny. Reading the same report: allow.

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.

Request:
an account manager in region North opens customer 8812.
Inputs:
user.region = north, customer.region = north.
Answer:
allow.

The same manager opens a customer in region South.

Answer:
deny.

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.

Where access control lists fit

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.

How a policy is written

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.

  • Visual editor or policy language.

    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.

  • Validated.

    A validator checks each policy before anyone can activate it.

  • Templates.

    A new policy can start from a template.

  • Versioned, with optional approval.

    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.

How a decision is explained

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.

Where the decision is enforced

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.

AI agents and machine identities

A service or an AI agent that calls your APIs signs in as a software client, and the same policies decide its requests.

  • Machine identities.

    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.

  • AI agents.

    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.

Further reading

  • The limits of RBAC: why role counts grow with every exception, with small policy examples for attributes, relationships and risk, and a sample decision trace that shows which rule denied a request.
  • Keycloak Authorization Services: how Keycloak's own authorization engine is built from resources, scopes, policies and permissions, and how it combines them into permit or deny.
  • Fine-grained authorization for AI agents: the approach our engineers presented at Java Day Istanbul 2026: one invoice-reading policy for an AI agent, evaluated against the agent, the user it acts for, the tenant and a risk score, with the same rule run on a developer machine and at the Access Gateway.

Questions

How do I add fine-grained authorization to Keycloak?

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 vs OpenFGA: which should I use?

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.

What is the best policy decision point to use with Keycloak?

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.

What is the difference between RBAC, ABAC and ReBAC?

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.

How do you implement fine-grained authorization for AI agents?

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.

How do I implement adaptive access policies that respond to real-time risk signals?

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.

Does identity software support fine-grained API access for machine identities?

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.

Bring a rule your roles cannot express.