Back to all resources
KBACKBACPython

KBAC: Step-Up Authentication Advice in Authorization Responses

Demonstrates authZEN 'advice' - when authorization is denied due to insufficient authentication level, the response includes guidance on what authentication step-up is needed to gain access.

KBAC: Step-Up Authentication Advice in Authorization Responses

This example demonstrates authZEN "advice" responses for step-up authentication:

What is advice?

When authorization is denied, the response can include guidance on HOW to get access (not just "denied").

Example flow:

1. The application asks whether alice may service her laptop, without a user token

2. The graph condition holds (alice OWNS the laptop), but the policy's filter on the token's exp claim fails, so KBAC denies and returns the advice written in the policy: insufficient_user_authentication, "Authentication is missing or expired"

3. The application sends the user to authenticate

4. The same request is repeated with alice's fresh user token in the Authorization header

5. The filter now holds and the decision is true

This enables progressive authentication - users only need a valid, recent token for sensitive operations.

Policy version note: the policy in this example uses "policy_version": "2.0-kbac". The same policy JSON is also accepted with "policy_version": "3.0-kbac" (identical schema, raw-Cypher semantics; do not reference $subject_id or external properties). Only 3.0-kbac additionally accepts USE graph.byName() routing and CALL { } subqueries for composite / data-residency IKGs - see resources authz-7 and authz-8.

Use case

Scenario: A user wants to service their laptop (e.g., wipe data, change settings).

Policy conditions:

1. Person must OWN the Laptop (graph condition: MATCH (subject:Person)-[:OWNS]->(resource:Laptop))

2. The user token's exp claim must be greater than a timestamp fixed in the policy (condition.filter: $token.exp > <timestamp>), i.e. the token must not be expired

3. Because the policy is 2.0-kbac, a user token sent with the request must belong to the requested subject (alice); otherwise the decision is false

Without a user token:

- Request: "Can alice service airbook-xyz?" with X-IK-ClientKey only

- The graph condition holds, $token.exp is null, the filter fails

- Response: {"decision": false, "context": {"advice": [{"error": "insufficient_user_authentication", "error_description": "Authentication is missing or expired", "sub": "$token.exp"}]}}

With alice's valid user token (Authorization: Bearer):

- Request: the same body

- Response: {"decision": true}

The advice is the map written by the policy author on the failing filter leaf; the application decides what to do with it (here: send the user to authenticate).

ikg

Requirements

Prerequisites:

- ServiceAccount credentials: For creating policies (Bearer token)

- AppAgent credentials: For data ingestion and authorization queries (X-IK-ClientKey)

- Auth0 (or similar) user token: For the user requiring access

Required API access:

- POST /capture/v1/nodes/ and /capture/v1/relationships/ (graph data)

- POST /configs/v1/authorization-policies (create policy)

- POST /access/v1/evaluation (authorization check with advice)

Steps

Step 1: Ingest Graph Data

- Authentication: AppAgent credential (X-IK-ClientKey header)

- Action: POST the shared AuthZEN dataset; the relevant part is Person(alice) -[OWNS]-> Laptop(airbook-xyz)

- Result: Graph ready for authorization queries

Step 2: Create Step-Up Policy

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST a 2.0-kbac policy with subject type Person, action CAN_SERVICE, resource type Laptop, cypher MATCH (subject:Person)-[:OWNS]->(resource:Laptop), and a condition.filter $token.exp > <timestamp> carrying an advice map (error, error_description, sub)

- Result: Policy ID returned

Step 3: Evaluation Without a User Token (Returns Advice)

- Authentication: AppAgent credential (X-IK-ClientKey header) only

- Request: POST /access/v1/evaluation, subject Person alice, resource Laptop airbook-xyz, action CAN_SERVICE

- Response: {"decision": false} with context.advice containing the policy's advice map

- The graph condition matched but the filter failed, so the advice is returned

Step 4: Evaluation With alice's User Token (Grants Access)

- Authentication: AppAgent credential + alice's user token (Authorization: Bearer header)

- Request: the same evaluation request

- Response: {"decision": true}

- The token's exp claim satisfies the filter, and the token's subject is the requested subject

Step 5: Cleanup

- Action: DELETE policy configuration

Step 1

Capture the nodes needed for this use case.

POST https://eu.api.indykite.com/capture/v1/nodes/Json
{
  "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 the relationships needed for this use case.

POST https://eu.api.indykite.com/capture/v1/relationships/Json
{
  "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

KBAC Policy: a Person can service a Laptop they OWN, provided the user token's exp claim is greater than the timestamp in the filter. The advice map on the filter leaf is returned when the filter fails.

policy.jsonJson
{
  "meta": {
    "policy_version": "2.0-kbac"
  },
  "subject": {
    "type": "Person"
  },
  "actions": [
    "CAN_SERVICE"
  ],
  "resource": {
    "type": "Laptop"
  },
  "condition": {
    "cypher": "MATCH (subject:Person)-[:OWNS]->(resource:Laptop)",
    "filter": {
      "attribute": "$token.exp",
      "operator": ">",
      "value": 1790787312,
      "advice": {
        "error": "insufficient_user_authentication",
        "error_description": "Authentication is missing or expired",
        "sub": "$token.exp"
      }
    }
  }
}

Request to create the KBAC Policy configuration using REST.

POST https://eu.api.indykite.com/configs/v1/authorization-policiesJson
{
  "project_id": "your_project_gid",
  "description": "description of policy",
  "display_name": "policy name",
  "name": "policy-name",
  "policy": "{\"meta\":{\"policy_version\":\"2.0-kbac\"},\"subject\":{\"type\":\"Person\"},\"actions\":[\"CAN_SERVICE\"],\"resource\":{\"type\":\"Laptop\"},\"condition\":{\"cypher\":\"MATCH (subject:Person)-[:OWNS]->(resource:Laptop)\",\"filter\":{\"attribute\":\"$token.exp\",\"operator\":\">\",\"value\":1790787312,\"advice\":{\"error\":\"insufficient_user_authentication\",\"error_description\":\"Authentication is missing or expired\",\"sub\":\"$token.exp\"}}}}",
  "status": "ACTIVE",
  "tags": []
}

Request to create the KBAC Policy configuration using REST.

policy_request.pyPython

import http.client

conn = http.client.HTTPSConnection("eu.api.indykite.com")

payload = "{"description": "",
  "display_name": "",
  "name": "",
  "policy": "",
  "project_id": "",
  "status": "ACTIVE",
  "tags": [
    ""
  ]}"
headers = {
    'Content-Type': "application/json",
    'Authorization': "YOUR_SECRET_TOKEN"
}

conn.request("POST", "/configs/v1/authorization-policies", payload, headers)
res = conn.getresponse()
data = res.read()

Request to read the KBAC Policy configuration using REST.

GET https://eu.api.indykite.com/configs/v1/authorization-policies/{id}Json
{
  "id": "your_policy_configuration_gid"
}

Request to read the KBAC Policy configuration using REST.

policy_request.pyPython

import http.client

conn = http.client.HTTPSConnection("eu.api.indykite.com")
headers = { 'Authorization': "YOUR_SECRET_TOKEN" }
conn.request("GET", "/configs/v1/authorization-policies/{{id}}", headers=headers)
res = conn.getresponse()
data = res.read()

Step 3

Run a KBAC authZEN Evaluation without a user token: the graph condition holds, the filter on $token.exp fails, and the advice is returned.

POST https://eu.api.indykite.com/access/v1/evaluationJson
{
  "subject": {
    "type": "Person",
    "id": "alice"
  },
  "resource": {
    "type": "Laptop",
    "id": "airbook-xyz"
  },
  "action": {
    "name": "CAN_SERVICE"
  }
}

Response to the KBAC authZEN Evaluation request without a user token: denied, with the policy's advice.

Response 200Json
{
  "context": {
    "advice": [
      {
        "error": "insufficient_user_authentication",
        "error_description": "Authentication is missing or expired",
        "sub": "$token.exp"
      }
    ]
  },
  "decision": false
}

Step 4

Add the Person node access token in the headers:

key:Authorization value: Bearer access_token_value

Then run the same KBAC authZEN Evaluation.

POST https://eu.api.indykite.com/access/v1/evaluationJson
{
  "subject": {
    "type": "Person",
    "id": "alice"
  },
  "resource": {
    "type": "Laptop",
    "id": "airbook-xyz"
  },
  "action": {
    "name": "CAN_SERVICE"
  }
}

The same evaluation using Python, with alice's user token in the Authorization header.

evaluation.pyPython

import http.client

conn = http.client.HTTPSConnection("eu.api.indykite.com")

payload = "{
  	"subject": {"type": "Person",
        "id": "alice"}, 
    "resource": {"type": "Laptop",
        "id": "airbook-xyz"},
    "action": {"name": "CAN_SERVICE"}
}"

headers = {
    'Content-Type': "application/json",
    'Authorization': "Bearer eyJh....",
    'X-IK-ClientKey': "eyJh...."
}

conn.request("POST", "/access/v1/evaluation", payload, headers)

res = conn.getresponse()
data = res.read()

Response to the KBAC authZEN Evaluation request with a valid user token: authorized.

Response 200Json
{
  "decision": true
}

Step 5

Delete the KBAC Policies.

DELETE https://eu.api.indykite.com/configs/v1/authorization-policies/{id}Json
{
  "id": "your_policy_configuration_gid"
}

Request to delete the KBAC Policies.

del.pyPython

import http.client

conn = http.client.HTTPSConnection("eu.api.indykite.com")

headers = { 'Authorization': "Bearer ...", 'Content-Type': "application/json" }

conn.request("DELETE", "/configs/v1/authorization-policies/{id}", headers=headers)

res = conn.getresponse()
data = res.read()