What is context-aware access control?
Context-aware access control (also called context-based access control, or CBAC) decides whether to allow a request by checking its circumstances, such as the time, the network, the device and how the user signed in, as well as who the user is and which roles they hold. The same user can be allowed in one context and refused in another.
In a classic role-based setup, a user with the finance role can open the payroll export at any hour, from any laptop, on any network. With context-aware rules you can keep that role but add conditions, for example that the export is only available during business hours, from the corporate network, and after the user has signed in with a second factor. The role still says what the user may do in principle, and the context rules check whether this particular request looks normal.
This guide explains the idea in plain terms, compares it with role-based (RBAC) and attribute-based (ABAC) access control, and then shows what Keycloak 26.x actually gives you to build it: time and regex policies in Authorization Services, the context attributes the policy engine exposes, claims your backend can push into an evaluation, JavaScript and Java policy providers, and conditional authentication flows at login. It also points out the parts Keycloak does not do on its own, such as geolocation and device posture, so you know where you need a gateway or an extension.
How is context-aware access control different from RBAC and ABAC?
Context-aware access control is best understood as a focus within attribute-based access control rather than a separate model. NIST SP 800-162 defines ABAC as granting or denying a subject’s request to perform an operation on an object based on the subject’s attributes, the object’s attributes, environment conditions and a policy written in terms of those. Context-aware access control leans on the environment conditions, which describe the conditions of the request rather than the people or data involved.
| Model | What the decision is based on | Typical rule | Where it fits |
|---|---|---|---|
| RBAC (role-based) | Roles assigned to the user | Users with finance may read payroll |
Stable job functions, coarse permissions |
| ABAC (attribute-based) | Attributes of the user and the resource, plus environment conditions | Users whose department matches the document’s department may edit it |
Fine-grained rules that roles alone cannot express |
| Context-aware (CBAC) | Environment and session attributes such as time, network, device and authentication strength | finance may export payroll only on weekdays, from the office network, after MFA |
Sensitive actions where the same user should be treated differently depending on the situation |
In practice you combine them. Roles keep the permission model readable, user and resource attributes handle ownership and tenancy, and context rules add a final check for the requests that carry the most risk. If you want the ABAC side in more depth, our introduction to attribute-based access control explains the model and the Keycloak ABAC configuration guide walks through attribute policies step by step.
Where does Keycloak evaluate context?
Keycloak can apply context at two different moments, and it helps to keep them apart because they use different features.
| Moment | Keycloak feature | What it can decide | Built-in context it can read |
|---|---|---|---|
| At sign-in | Conditional sub-flows in authentication flows | Whether to ask for a second factor, step the user up to a stronger method, or deny the login | User role, user attribute, configured credentials, requested client scope, level of authentication |
| At each protected request | Authorization Services policies | Whether a token holder may use a specific resource and scope | Time, client IP and host, user agent, client ID, realm, token claims and claims pushed by your backend |
The sign-in side is what most people call conditional access, and our explainer on conditional access covers that model in detail. The rest of this guide spends most of its time on the request side, because that is where Keycloak exposes the environment attributes that context-aware rules depend on.
Keycloak Authorization Components
Keycloak’s Authorization Services are a policy engine attached to a client. You switch them on by turning on Client authentication and Authorization in the client’s settings, which turns that client into a resource server (the application whose resources are being protected). After that, an Authorization tab appears on the client with the pieces below.
| Component | What it is | Role in context-aware access |
|---|---|---|
| Resources | The things you protect, such as /api/payroll/* or a document type |
Say what needs a context check |
| Scopes | The actions on a resource, such as read, export or approve |
Let you put context rules on risky actions only |
| Policies | Conditions that grant or deny, such as a role, a time window or a regex on an attribute | Hold the context logic itself |
| Permissions | Bindings of resources or scopes to policies, with a decision strategy | Combine a role policy with a context policy for one action |
| Policy decision and enforcement points | Keycloak evaluates policies (the policy decision point, or PDP) and your application or gateway enforces the answer (the policy enforcement point, or PEP) | Keep decisions central while each service enforces them |
The decision strategy on a permission matters for context rules. With Unanimous, every attached policy must grant, so a role policy plus a time policy means “has the role and it is business hours”. With Affirmative, any one granting policy is enough, which is rarely what you want when the context check is supposed to restrict access. The policy types explainer compares every built-in policy type and strategy if you are new to this part of Keycloak.
What context data does Keycloak have during policy evaluation?
When Keycloak evaluates a policy, it builds an evaluation context with two parts: the identity (built from the access token sent with the authorization request, so any claim you add to that token with a protocol mapper, the Keycloak setting that puts extra claims in tokens, is available) and a set of runtime attributes. The Evaluation API documentation lists the built-in runtime attributes:
| Attribute | What it contains |
|---|---|
kc.time.date_time |
The current date and time on the Keycloak server |
kc.client.network.ip_address |
The IP address of the caller that sent the authorization request |
kc.client.network.host |
The caller’s host as Keycloak sees it, usually the IP address or what the proxy headers provide |
kc.client.id |
The client ID the access token was issued for |
kc.client.user_agent |
The User-Agent header of the authorization request |
kc.realm.name |
The realm name |
The network attributes need some care. They describe whoever called Keycloak’s token endpoint for the authorization decision, and in many architectures that caller is your backend or API gateway rather than the user’s browser. If a backend asks Keycloak for a decision on the user’s behalf, kc.client.network.ip_address is the backend’s address. Also, when Keycloak runs behind a load balancer, it only sees the real client address if you configure the proxy-headers option (or the PROXY protocol when the proxy passes TLS through), as described in the reverse proxy guide.
The way to get the user’s real context into the decision is to push it as claims. Keycloak lets the caller of an authorization request send a claim_token, a Base64-encoded JSON object whose values are arrays of strings, and those claims become evaluation attributes that your policies can read. The pushing claims documentation notes that claims starting with kc. are reserved and filtered out, so a caller cannot overwrite the server’s own time or client attributes. Keycloak itself has no built-in geolocation, device posture or risk score, so if you want those signals, your gateway or backend computes them and pushes them in the same way.
Managed identity, built on open source
Skycloak runs real upstream Keycloak for you, so you get SSO, MFA and SCIM without operating the infrastructure. Unlimited users on every plan, no fork, and you can export at any time.
Setting Up Context-Aware Policies
The examples below assume a confidential client called payroll-api with Authorization turned on, a resource for the payroll export and an export scope. You can create every policy in the admin console under Clients, then payroll-api, then Authorization, then Policies and Create client policy, or through the Admin REST API as shown.
Time-based access rules
A time policy grants access only when the current time falls inside the window you set. Its fields, described in the time-based policy documentation, are a start time and expire time for one-off windows, plus repeating ranges for day of month, month, year, hour and minute (the admin console form shows every range except year, which you can set through the REST API). To allow exports from 09:00 to 16:59 every day:
- Open Policies, choose Create client policy and pick Time.
- Name it
business-hours, turn on Repeat, and set Hour to9and the end of the hour range to16. The range is inclusive of the whole hour, so16allows anything up to 16:59. - Leave Logic as Positive and save.
The same policy through the Admin REST API looks like this:
curl -X POST \
"https://keycloak.example.com/admin/realms/acme/clients/$CLIENT_UUID/authz/resource-server/policy/time" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "business-hours",
"hour": "9",
"hourEnd": "16",
"logic": "POSITIVE"
}'
Two limits are worth knowing before you rely on time rules. First, the policy has no day-of-week field, so “weekdays only” cannot be expressed with a time policy alone; you need a JavaScript policy or a claim pushed by your backend for that. Second, the hours are compared in the Keycloak server’s time zone (the JVM default), not the user’s, and container images usually run in UTC, so a “9 to 5 in New York” rule has to be written in server time or handled in a script.
Regex policies on context attributes
A regex policy compares one value against a regular expression. By default it reads a claim from the user’s identity, but if you turn on Target Context Attributes it reads from the evaluation attributes instead, which include both the built-in kc.* values and any claims your backend pushed. That makes it the simplest built-in way to write a network rule. The regex policy documentation covers the dot notation for nested claims.
This policy grants access only when a pushed client_ip claim falls inside the 10.20.0.0/16 office range:
{
"name": "corporate-network",
"targetClaim": "client_ip",
"pattern": "10\\.20\\.\\d{1,3}\\.\\d{1,3}",
"targetContextAttributes": true,
"logic": "POSITIVE"
}
You send that body to the same endpoint as above, ending in /policy/regex. Keycloak requires the whole value to match the pattern, so you do not need ^ and $ anchors. Regular expressions are awkward for matching IP ranges, so for anything beyond a simple prefix it is cleaner to let the gateway decide “is this a corporate address” and push a claim such as network: ["corporate"].
Pushing request context from your backend
Your backend or gateway sees the real request, so it is the right place to collect context. It then asks Keycloak for a decision with the user’s access token and a claim_token that carries the context. Here the claim token is the Base64 encoding of {"client_ip":["203.0.113.7"],"device_trust":["managed"]}:
curl -X POST \
"https://keycloak.example.com/realms/acme/protocol/openid-connect/token" \
-H "Authorization: Bearer $USER_ACCESS_TOKEN" \
--data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
--data "audience=payroll-api" \
--data "permission=payroll-export#export" \
--data "response_mode=decision" \
--data "claim_token=eyJjbGllbnRfaXAiOlsiMjAzLjAuMTEzLjciXSwiZGV2aWNlX3RydXN0IjpbIm1hbmFnZWQiXX0=" \
--data "claim_token_format=urn:ietf:params:oauth:token-type:jwt"
With response_mode=decision, Keycloak answers with {"result": true} when the permission is granted and an error when it is not, which is all a gateway needs. Pushed claims are only as trustworthy as whoever sends them, so send them from a backend you control and never let a browser or mobile app supply its own device_trust value. Keycloak already rejects a claim_token sent by a public client without a permission ticket, which closes the most obvious route.
JavaScript policy creation
When a rule needs logic the built-in types cannot express, such as “weekdays only” or “auditors only from managed devices”, a JavaScript policy can read the identity and the evaluation attributes together. This example grants the export only to users with the auditor realm role whose request was marked as coming from a managed device:
const context = $evaluation.getContext();
const identity = context.getIdentity();
const attributes = context.getAttributes();
const managedDevice = attributes.containsValue('device_trust', 'managed');
if (identity.hasRealmRole('auditor') && managedDevice) {
$evaluation.grant();
}
A policy that never calls grant() leaves the evaluation denied, so you do not need an explicit deny() branch. Keycloak does not let you paste scripts into the admin console. As the JavaScript policy documentation explains, you package scripts in a JAR with a META-INF/keycloak-scripts.json descriptor, copy it to the server’s providers/ directory and run kc.sh build:
{
"policies": [
{
"name": "Auditor on managed device",
"fileName": "auditor-managed-device.js",
"description": "Grants export to auditors on managed devices"
}
]
}
The policy then appears as its own type in the policy list. JavaScript policies, including ones deployed from a JAR, belong to the scripts feature, which the server development guide marks as a preview feature. It is off by default, so start the server with --features=scripts (or --features=preview), otherwise the policy type does not appear.
Custom SPI development
If you need context that requires Java, such as calling a device management service during evaluation, you can write your own policy type. A custom policy is a class that implements org.keycloak.authorization.policy.provider.PolicyProvider, whose single evaluate(Evaluation evaluation) method calls grant() when the conditions hold, plus a factory that implements PolicyProviderFactory:
public class DeviceTrustPolicyProvider implements PolicyProvider {
@Override
public void evaluate(Evaluation evaluation) {
Attributes attributes = evaluation.getContext().getAttributes();
if (attributes.containsValue("device_trust", "managed")) {
evaluation.grant();
}
}
@Override
public void close() {
}
}
The factory implements PolicyProviderFactory<YourRepresentation>: it returns the provider from create(AuthorizationProvider authorization), gives the policy type an ID, a display name and a group, converts a stored policy to its representation (toRepresentation and getRepresentationType), and is registered in META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory inside your JAR. These interfaces live in Keycloak’s server-spi-private module, which means they can change between releases, so pin your Keycloak version and retest the provider on every upgrade. On Skycloak, you upload your own provider JAR through Extensions; it is included on the Enterprise plan and available on Business with the Bring Your Own Extension add-on.
Context at sign-in: conditional authentication flows
The second place Keycloak uses context is the login itself. An authentication flow can contain a Conditional sub-flow that only runs when all of its condition executions are true. Keycloak 26.4 and later ships these conditions (client scope and sub-flow executed arrived in 26.1, credential in 26.4):
Condition - user role: the user has a given realm or client role.Condition - user attribute: the user (and optionally their groups) has an attribute with a given value, or does not have it if you negate the result.Condition - user configured: the user has set up the credential the next step needs, such as an OTP device.Condition - client scope: a given client scope applies to the login, either because it is a default scope of the client or because the client requested it as an optional scope.Condition - credential: the user did or did not authenticate with certain credential types earlier in the flow.Condition - sub-flow executed: an earlier sub-flow did or did not run.Condition - Level of Authentication: the application asked for a stronger level of authentication, which is how step-up authentication works.
Combined with the Deny access execution, these let you build rules such as “users with the admin role must use MFA” or “the payments scope requires a passkey”. The step-up authentication guide shows the Level of Authentication setup end to end. Note that none of the built-in conditions look at the IP address, the time or the location of the login. For those you either put the check in a proxy in front of Keycloak or write a custom authenticator with the Authentication SPI.
CBAC use cases
Banking security examples
Financial applications are the classic case, because the same user and the same action can be routine or suspicious depending on the situation. Here is how the usual context factors map onto concrete Keycloak features:
| Context factor | Rule | How to build it in Keycloak |
|---|---|---|
| Time pattern | Large transfers only during staffed hours | Time policy on the transfer-large scope, combined unanimously with the role policy |
| Location | Extra verification from a new country | Gateway computes the country and pushes it as a claim; a regex or JavaScript policy checks it, and a denial sends the user through a step-up login |
| Device profile | Unfamiliar devices need a second factor | Backend pushes device_trust; for login, a conditional flow forces MFA for the relevant role |
| Transaction value | Lower limits when risk is higher | Keep the amount check in the application, and use Keycloak to decide which role and context may approve above a threshold |
For high-value actions where the decision depends on history (for example “the user has never sent money to this account before”), the logic usually belongs in the application or a fraud service, and Keycloak’s job is to require a fresh, strong authentication before the action runs.
API protection methods
For APIs, context-aware rules typically restrict who may call sensitive scopes from where. A gateway in front of the API acts as the enforcement point: it validates the access token, collects the caller’s IP address or network zone, pushes them as claims and asks Keycloak for a decision on the requested resource and scope.
Our guide to Keycloak token validation for APIs covers the validation half, and if your decisions already live in a dedicated policy engine, integrating external PDPs with Keycloak shows how to keep Keycloak as the identity source while another service decides. Rate limiting and request throttling are not Keycloak features, so keep those in the gateway and use Keycloak for the permission decision.
CBAC implementation guidelines
Policy design tips
Keep each policy about one condition, so business-hours, corporate-network and managed-device are separate policies that you combine in permissions. That makes it obvious from the permission list which context rules apply to which action, and it lets you reuse a rule across resources. Only use context attributes that actually change the decision, and decide in advance what happens when a value is missing. A regex policy with no value to read does not grant, so a missing pushed claim results in denial, which is the safe default but can surprise you during rollout.
Be careful with attributes users can edit. The JavaScript policy documentation warns that if a policy relies on user attributes, those attributes must be read-only for the user, otherwise a user could change their own department and their own access with it. The user profile settings let you make an attribute editable by admins only.
Performance optimization
Context checks run on every authorization request, so a few habits keep them cheap. Prefer the built-in time and regex policies over JavaScript where they are enough, because they need no script engine. Collect expensive signals such as device posture once in the gateway and push the result, instead of calling external services from inside a policy. Put context rules only on the scopes that need them rather than on every resource. Finally, if you run many services through Keycloak, keep in mind that each decision is a call to the token endpoint, and size Keycloak for that request rate rather than for the number of registered users.
Security monitoring
Before you enforce a new context rule, use the Evaluate tab on the client’s Authorization page to simulate a request for a given user, client and set of context attributes, and check which policies granted or denied. The resource server’s Policy enforcement mode can be set to Permissive while you roll out, which lets requests through when no policy is associated with a resource, but it does not soften policies that exist, so test with Evaluate first.
Once live, turn on login and admin events so that denied logins, step-up challenges and policy changes leave a record; our Keycloak auditing and event logging guide explains which events to keep and where to send them, and Skycloak’s audit logs keep them for you on a managed deployment.
FAQs
What is context-based access control?
Context-based access control is an access model that allows or denies a request based on its circumstances, such as time, network, device and authentication strength, in addition to the user’s identity and roles. It is usually implemented as a set of environment conditions layered on top of role-based or attribute-based rules.
What is the difference between context-aware access control and ABAC?
ABAC is the broader model: it evaluates attributes of the user and the resource together with environment conditions. Context-aware access control is the part of ABAC that focuses on environment and session attributes, such as where and when a request happens, rather than on who the user is or what the resource contains.
Can Keycloak restrict access by IP address or location?
Yes for IP addresses, with a regex policy that has Target Context Attributes turned on and reads kc.client.network.ip_address. That attribute is the address of whatever called Keycloak’s token endpoint, so when a backend asks on the user’s behalf, push the user’s IP as a claim and match that instead. Keycloak has no built-in geolocation, so for country or region rules a gateway or backend looks up the location and pushes it as a claim that a policy then checks.
What is a policy enforcement point in Keycloak?
The policy enforcement point (PEP) is the component that intercepts a request and enforces the decision, which in a Keycloak setup is your application, its Keycloak policy enforcer library or an API gateway. Keycloak acts as the policy decision point (PDP) that evaluates the policies and returns a grant or a denial.
Does context-aware access control replace MFA?
No. MFA proves who the user is with more than one factor, while context-aware access control decides when that proof is needed and what the user may do afterwards. In Keycloak the two work together: conditional flows decide when to ask for MFA, and authorization policies decide which actions need a strong, recent login.
Conclusion
Context-aware access control lets you keep a simple role model and add conditions only where the risk justifies it. In Keycloak 26.x you build it from two pieces: conditional authentication flows for decisions at sign-in, and Authorization Services policies for decisions on each request. Time and regex policies cover many rules without code, pushed claims bring in the real request context from your gateway, and JavaScript or Java policy providers handle the rest. Start with one sensitive action, write one or two context policies for it, test them with the Evaluate tab and then widen the coverage.
If you would rather spend that time on policies than on running Keycloak, Skycloak hosts and operates it for you, with advanced security settings and role-based access control built in. You can compare the plans to see what fits your team.

