KBAC: Token-Scope-Gated Read/Write Permissions
This example demonstrates KBAC policies that take both the graph and the bearer token's scope claim into account.
Pattern:
- The condition.cypher establishes the relationship-based base permission (e.g., subject DRIVES resource).
- The condition.filter narrows the action by inspecting $token.scope with the CONTAINS operator:
- CAN_READ requires the scope claim to contain 'cars.read'.
- CAN_WRITE requires the scope claim to contain 'cars.write'.
- Two separate policies coexist; the runtime selects them by action.
Effect: a single user identity (Knight Rider, who DRIVES KITT) can be authorized for CAN_READ today but denied CAN_WRITE because the issued bearer token does not include the cars.write scope. Re-authenticate with a wider-scoped token to gain CAN_WRITE without changing any policy.
Use case
Scenario: A vehicle service portal issues bearer tokens with scopes that match what the user can actually do this session.
- Mechanic token: scope = "cars.read cars.write" -> can read AND modify car records.
- Customer token: scope = "cars.read" -> can read their own car record, cannot modify it.
Policies:
1. CAN_READ on Car when subject DRIVES car AND $token.scope contains 'cars.read'.
2. CAN_WRITE on Car when subject DRIVES car AND $token.scope contains 'cars.write'.
Calls:
- Knight Rider with cars.read token: CAN_READ KITT -> true; CAN_WRITE KITT -> false.
- Mechanic with full scopes: both -> true.
This is the cleanest place to put scope checks: in the policy condition, not in the client.

Requirements
Prerequisites:
- ServiceAccount credentials: For creating the policies.
- AppAgent credentials: For calling /access/v1/evaluation.
- A Token Introspect configuration so the API can verify the bearer token and extract its scope claim.
- Graph data with DRIVES relationships.
Required API access:
- POST /configs/v1/authorization-policies (one per action)
- POST /access/v1/evaluation (caller passes the user's bearer token in the Authorization header)
Steps
Step 1: Capture Graph Data
- Action: POST Person and Car nodes plus DRIVES relationships (re-use the authZEN dataset).
Step 2: Create the CAN_READ Policy
- Action: POST a policy whose condition.filter requires $token.scope to CONTAIN 'cars.read'.
Step 3: Create the CAN_WRITE Policy
- Action: POST a similar policy requiring $token.scope to CONTAIN 'cars.write'.
Step 4: Evaluate with a Narrow-Scope Token
- Action: POST /access/v1/evaluation with action CAN_READ, sending Authorization: Bearer <token> where the token's scope claim contains only cars.read.
- Result: authorized.
Step 5: Evaluate the Same Subject for CAN_WRITE
- Action: POST /access/v1/evaluation with action CAN_WRITE using the same narrow-scope token.
- Result: denied - the scope check fails even though DRIVES exists.
Step 1
Capture Person and Car nodes.
{
"nodes": [
{
"external_id": "alice",
"is_identity": true,
"type": "Person",
"properties": [
{
"type": "email",
"value": "alice@email.com"
},
{
"type": "given_name",
"value": "Alice"
},
{
"type": "last_name",
"value": "Smith"
}
]
},
{
"external_id": "knightrider",
"type": "Person",
"is_identity": true,
"properties": [
{
"type": "email",
"value": "knightrider@demo.com"
},
{
"type": "name",
"value": "Michael Knight"
}
]
},
{
"external_id": "satchmo",
"type": "Person",
"is_identity": true,
"properties": [
{
"type": "email",
"value": "satchmo@demo.com"
},
{
"type": "name",
"value": "Louis Armstrong"
}
]
},
{
"external_id": "karel",
"type": "Person",
"is_identity": true,
"properties": [
{
"type": "email",
"value": "karel@demo.com"
},
{
"type": "name",
"value": "Karel Plihal"
}
]
},
{
"external_id": "kitt",
"type": "Car",
"is_identity": false,
"properties": [
{
"type": "manufacturer",
"value": "pontiac"
},
{
"type": "model",
"value": "Firebird"
}
]
},
{
"external_id": "cadillacv16",
"type": "Car",
"is_identity": false,
"properties": [
{
"type": "manufacturer",
"value": "Cadillac"
},
{
"type": "model",
"value": "V-16"
}
]
},
{
"external_id": "harmonika",
"type": "Bus",
"is_identity": false,
"properties": [
{
"type": "manufacturer",
"value": "Ikarus"
},
{
"type": "model",
"value": "280"
}
]
},
{
"external_id": "listek",
"type": "Ticket",
"is_identity": false
},
{
"external_id": "airbook-xyz",
"type": "Laptop",
"is_identity": false
}
]
}Capture DRIVES relationships.
{
"relationships": [
{
"source": {
"external_id": "knightrider",
"type": "Person"
},
"target": {
"external_id": "kitt",
"type": "Car"
},
"type": "DRIVES"
},
{
"source": {
"external_id": "satchmo",
"type": "Person"
},
"target": {
"external_id": "cadillacv16",
"type": "Car"
},
"type": "DRIVES"
},
{
"source": {
"external_id": "karel",
"type": "Person"
},
"target": {
"external_id": "listek",
"type": "Ticket"
},
"type": "HAS"
},
{
"source": {
"external_id": "listek",
"type": "Ticket"
},
"target": {
"external_id": "harmonika",
"type": "Bus"
},
"type": "FOR"
},
{
"source": {
"external_id": "karel",
"type": "Person"
},
"target": {
"external_id": "airbook-xyz",
"type": "Laptop"
},
"type": "OWNS"
},
{
"source": {
"external_id": "alice",
"type": "Person"
},
"target": {
"external_id": "airbook-xyz",
"type": "Laptop"
},
"type": "OWNS"
},
{
"source": {
"external_id": "knightrider",
"type": "Person"
},
"target": {
"external_id": "kitt",
"type": "Car"
},
"type": "OWNS"
},
{
"source": {
"external_id": "alice",
"type": "Person"
},
"target": {
"external_id": "cadillacv16",
"type": "Car"
},
"type": "DRIVES"
}
]
}Step 2
Policy: CAN_READ on Car requires the bearer token's scope claim to contain 'cars.read'.
{
"meta": {
"policy_version": "2.0-kbac"
},
"subject": {
"type": "Person"
},
"actions": [
"CAN_READ"
],
"resource": {
"type": "Car"
},
"condition": {
"cypher": "MATCH (subject:Person)-[:DRIVES]->(resource:Car)",
"filter": {
"operator": "CONTAINS",
"attribute": "$token.scope",
"value": "cars.read"
}
}
}Request to create the CAN_READ policy.
{
"project_id": "your_project_gid",
"description": "Authorize CAN_READ on Car only when the access token's scope claim contains 'cars.read'.",
"display_name": "policy - scope cars.read",
"name": "policy-scope-cars-read",
"policy": "{\"meta\":{\"policy_version\":\"2.0-kbac\"},\"subject\":{\"type\":\"Person\"},\"actions\":[\"CAN_READ\"],\"resource\":{\"type\":\"Car\"},\"condition\":{\"cypher\":\"MATCH (subject:Person)-[:DRIVES]->(resource:Car)\",\"filter\":{\"operator\":\"CONTAINS\",\"attribute\":\"$token.scope\",\"value\":\"cars.read\"}}}",
"status": "ACTIVE",
"tags": []
}Step 3
Policy: CAN_WRITE on Car requires the bearer token's scope claim to contain 'cars.write'.
{
"meta": {
"policy_version": "2.0-kbac"
},
"subject": {
"type": "Person"
},
"actions": [
"CAN_WRITE"
],
"resource": {
"type": "Car"
},
"condition": {
"cypher": "MATCH (subject:Person)-[:DRIVES]->(resource:Car)",
"filter": {
"operator": "CONTAINS",
"attribute": "$token.scope",
"value": "cars.write"
}
}
}Request to create the CAN_WRITE policy.
{
"project_id": "your_project_gid",
"description": "Authorize CAN_WRITE on Car only when the access token's scope claim contains 'cars.write'.",
"display_name": "policy - scope cars.write",
"name": "policy-scope-cars-write",
"policy": "{\"meta\":{\"policy_version\":\"2.0-kbac\"},\"subject\":{\"type\":\"Person\"},\"actions\":[\"CAN_WRITE\"],\"resource\":{\"type\":\"Car\"},\"condition\":{\"cypher\":\"MATCH (subject:Person)-[:DRIVES]->(resource:Car)\",\"filter\":{\"operator\":\"CONTAINS\",\"attribute\":\"$token.scope\",\"value\":\"cars.write\"}}}",
"status": "ACTIVE",
"tags": []
}Step 4
Evaluation: Knight Rider asks if he can CAN_READ KITT. Send Authorization: Bearer <token> where the token's scope claim contains cars.read (plus X-IK-ClientKey for the AppAgent).
{
"subject": {
"type": "Person",
"id": "knightrider"
},
"resource": {
"type": "Car",
"id": "kitt"
},
"action": {
"name": "CAN_READ"
}
}Response - authorized (DRIVES exists and the token scope contains cars.read).
{
"decision": true
}Step 5
Same subject and resource, but action is CAN_WRITE and the same token only carries cars.read. The condition.filter scope check denies.
{
"subject": {
"type": "Person",
"id": "knightrider"
},
"resource": {
"type": "Car",
"id": "kitt"
},
"action": {
"name": "CAN_WRITE"
}
}Response - denied. The DRIVES relationship is present but the scope check fails.
{
"decision": false
}Same CAN_READ evaluation in Python (token passed in the Authorization header).
import http.client
import json
conn = http.client.HTTPSConnection("eu.api.indykite.com")
payload = json.dumps({
"subject": {"type": "Person", "id": "knightrider"},
"resource": {"type": "Car", "id": "kitt"},
"action": {"name": "CAN_READ"}
})
headers = {
'Content-Type': "application/json",
'X-IK-ClientKey': "",
# User access token whose scope claim must contain 'cars.read'
'Authorization': "Bearer <token>"
}
conn.request("POST", "/access/v1/evaluation", payload, headers)
res = conn.getresponse()
data = res.read()
API Endpoints
/configs/v1/authorization-policies /configs/v1/token-introspects /access/v1/evaluation