Back to all resources
ContX IQ ContX IQ Json

ContX IQ: Step-Up Authentication Based on auth_time Claim

Enforce re-authentication for sensitive operations by checking the token's auth_time claim. If the user authenticated more than one hour ago, return an advice requesting fresh authentication.

ContX IQ: Step-Up Authentication Based on auth_time Claim

This example enforces session freshness using the auth_time JWT claim:

What is auth_time?

The auth_time claim indicates when the user last actively authenticated (entered credentials).

It's different from iat (issued at) - a refresh token can have a recent iat but old auth_time.

Policy logic:

- Calculate time since authentication: now() - auth_time

- If > 1 hour: Return advice "Please re-authenticate"

- If <= 1 hour: Allow the operation

Use case: Sensitive operations (payment changes, account deletion) require recent authentication.

Use case

Scenario: A user wants to change their payment method, which requires recent authentication.

Token claims:

- auth_time: 2024-01-15T10:00:00Z (when user entered password)

- Current time: 2024-01-15T11:30:00Z

- Time since auth: 1.5 hours (exceeds 1 hour limit)

Request flow:

1. User (with valid but stale token) requests payment change

2. Policy checks: auth_time > 1 hour ago?

3. Response: {decision: false, advice: {action: "reauthenticate", reason: "session_too_old"}}

4. Application prompts user to sign in again

5. User re-authenticates, gets new token with fresh auth_time

6. Same request now succeeds

This pattern ensures sensitive operations have fresh authentication without invalidating the entire session.

ikg

Requirements

Prerequisites:

- ServiceAccount credentials: For creating policies and queries (Bearer token)

- AppAgent credentials: For data ingestion and query execution (X-IK-ClientKey)

- User access token: JWT with auth_time claim from your IdP

- Token Introspect configuration: Must extract auth_time claim

JWT auth_time claim:

- Standard OIDC claim indicating last authentication time

- Set by IdP when user actively authenticates

- Not updated on token refresh (unlike iat)

Steps

Step 1: Ingest Person and PaymentMethod Data

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

- Action: POST Person, PaymentMethod, Company nodes and relationships

- Result: Graph ready for payment operations

Step 2: Create Step-Up Policy with auth_time Check

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST policy with conditions:

- Check: auth_age parameter (calculated from auth_time at introspection)

- If auth_age > 3600 (seconds): Return advice to re-authenticate

- If auth_age <= 3600: Allow operation

- Result: Policy ID returned

Step 3: Create Payment Change Query

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST query for payment method operations

- The query will either succeed or return step-up advice based on auth_time

- Result: Query ID returned

Step 4: Execute with Stale Token

- Authentication: AppAgent credential + User token (with old auth_time)

- Action: POST to /contx-iq/v1/execute

- Result: {decision: false, advice: {reauthenticate: true}} if auth_time too old

- Result: Success if auth_time is recent

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": "bob",
      "type": "Person",
      "is_identity": true,
      "properties": [
        {
          "type": "email",
          "value": "bob@email.com"
        },
        {
          "type": "given_name",
          "value": "Bob"
        }
      ]
    },
    {
      "external_id": "cb123",
      "type": "PaymentMethod",
      "properties": [
        {
          "type": "payment_name",
          "value": "Credit Card"
        }
      ]
    },
    {
      "external_id": "kl123",
      "type": "PaymentMethod",
      "properties": [
        {
          "type": "payment_name",
          "value": "Klarna"
        }
      ]
    },
    {
      "external_id": "ct123",
      "type": "Contract",
      "properties": [
        {
          "type": "category",
          "value": "Parking"
        },
        {
          "type": "status",
          "value": "Active"
        }
      ]
    },
    {
      "external_id": "ct234",
      "type": "Contract",
      "properties": [
        {
          "type": "category",
          "value": "Parking"
        },
        {
          "type": "status",
          "value": "Active"
        }
      ]
    },
    {
      "external_id": "car1",
      "type": "Vehicle",
      "properties": [
        {
          "type": "category",
          "value": "Car"
        },
        {
          "type": "is_active",
          "value": true
        },
        {
          "type": "vin",
          "value": "rtfhcnvjt471"
        }
      ]
    },
    {
      "external_id": "car2",
      "type": "Vehicle",
      "properties": [
        {
          "type": "category",
          "value": "Car"
        },
        {
          "type": "is_active",
          "value": true
        },
        {
          "type": "vin",
          "value": "kdcbfrt178"
        }
      ]
    },
    {
      "external_id": "license1",
      "type": "LicenseNumber",
      "properties": [
        {
          "type": "status",
          "value": "Active"
        },
        {
          "type": "number",
          "value": "AX123456",
          "metadata": {
            "assurance_level": 3,
            "source": "BRREG"
          }
        }
      ]
    },
    {
      "external_id": "license2",
      "type": "LicenseNumber",
      "properties": [
        {
          "type": "status",
          "value": "Active"
        },
        {
          "type": "number",
          "value": "OL123456",
          "metadata": {
            "assurance_level": 3,
            "source": "BRREG"
          }
        }
      ]
    },
    {
      "external_id": "companyParking",
      "type": "Company",
      "properties": [
        {
          "type": "name",
          "value": "City Parking Inc"
        }
      ]
    }
  ]
}

Capture the relationships needed for this use case.

POST https://eu.api.indykite.com/capture/v1/relationships/Json
{
  "relationships": [
    {
      "source": {
        "external_id": "bob",
        "type": "Person"
      },
      "target": {
        "external_id": "kl123",
        "type": "PaymentMethod"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "alice",
        "type": "Person"
      },
      "target": {
        "external_id": "cb123",
        "type": "PaymentMethod"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "alice",
        "type": "Person"
      },
      "target": {
        "external_id": "ct123",
        "type": "Contract"
      },
      "type": "ACCEPTED"
    },
    {
      "source": {
        "external_id": "bob",
        "type": "Person"
      },
      "target": {
        "external_id": "ct234",
        "type": "Contract"
      },
      "type": "ACCEPTED"
    },
    {
      "source": {
        "external_id": "ct123",
        "type": "Contract"
      },
      "target": {
        "external_id": "car1",
        "type": "Vehicle"
      },
      "type": "COVERS"
    },
    {
      "source": {
        "external_id": "ct234",
        "type": "Contract"
      },
      "target": {
        "external_id": "car2",
        "type": "Vehicle"
      },
      "type": "COVERS"
    },
    {
      "source": {
        "external_id": "car1",
        "type": "Vehicle"
      },
      "target": {
        "external_id": "license1",
        "type": "LicenseNumber"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "car2",
        "type": "Vehicle"
      },
      "target": {
        "external_id": "license2",
        "type": "LicenseNumber"
      },
      "type": "HAS"
    },
    {
      "source": {
        "external_id": "companyParking",
        "type": "Company"
      },
      "target": {
        "external_id": "ct234",
        "type": "Contract"
      },
      "type": "OFFERS"
    },
    {
      "source": {
        "external_id": "companyParking",
        "type": "Company"
      },
      "target": {
        "external_id": "ct123",
        "type": "Contract"
      },
      "type": "OFFERS"
    }
  ]
}

Step 2

Create a CIQ Policy which designates an advice step up if the access token auth_time claim is more than an hour ago using the parameter auth_age which is set at token introspection time.

policy.jsonJson
{
  "meta": {
    "policy_version": "1.0-ciq"
  },
  "subject": {
    "type": "Person"
  },
  "condition": {
    "cypher": "MATCH (company:Company)-[:OFFERS]->(contract:Contract)<-[:ACCEPTED]-(subject:Person)-[:HAS]->(payment:PaymentMethod), (contract)-[:COVERS]->(vehicle:Vehicle)-[:HAS]->(ln:LicenseNumber)",
    "filter": [
      {
        "operator": "AND",
        "operands": [
          {
            "attribute": "subject.external_id",
            "operator": "=",
            "value": "$subject_external_id"
          },
          {
            "attribute": "$token.sub",
            "operator": "=",
            "value": "$token_sub"
          }
        ]
      }
    ],
    "token_filter": {
      "operator": "<=",
      "attribute": "$token.auth_age",
      "value": "3600",
      "advice": {
        "error": "insufficient_user_authentication",
        "error_description": "More recent authentication is required, max_age= 3600"
      }
    }
  },
  "allowed_reads": {
    "nodes": [
      "company.*",
      "subject.*",
      "payment.*"
    ]
  }
}

Request to create the CIQ 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\":\"1.0-ciq\"},\"subject\":{\"type\":\"Person\"},\"condition\":{\"cypher\":\"MATCH (company:Company)-[:OFFERS]->(contract:Contract)<-[:ACCEPTED]-(subject:Person)-[:HAS]->(payment:PaymentMethod), (contract)-[:COVERS]->(vehicle:Vehicle)-[:HAS]->(ln:LicenseNumber)\",\"filter\":[{\"operator\":\"AND\",\"operands\":[{\"attribute\":\"subject.external_id\",\"operator\":\"=\",\"value\":\"$subject_external_id\"},{\"attribute\":\"$token.sub\",\"operator\":\"=\",\"value\":\"$token_sub\"}]}],\"token_filter\":{\"operator\":\"<=\",\"attribute\":\"$token.auth_age\",\"value\":\"3600\",\"advice\":{\"error\":\"insufficient_user_authentication\",\"error_description\":\"More recent authentication is required, max_age= 3600\"}}},\"allowed_reads\":{\"nodes\":[\"company.*\",\"subject.*\",\"payment.*\"]}}",
  "status": "ACTIVE",
  "tags": []
}

Request to read the CIQ Policy configuration using REST.

policy_request.jsonJson
{
  "id": "your_policy_configuration_gid"
}

Step 3

Create a CIQ Query in the context of the policy to configure the step up advice.

knowledge_query.jsonJson
{
  "nodes": [
    "company.external_id",
    "payment.external_id"
  ]
}

Request to create a CIQ Query configuration using REST.

POST https://eu.api.indykite.com/configs/v1/knowledge-queriesJson
{
  "project_id": "your_project_gid",
  "description": "description of knowledge query",
  "display_name": "knowledge query name",
  "name": "knowledge-query-name",
  "policy_id": "your_policy_gid",
  "query": "{\"nodes\":[\"company.external_id\",\"payment.external_id\"]}",
  "status": "ACTIVE"
}

Read the CIQ Query Configuration.

GET https://eu.api.indykite.com/configs/v1/knowledge-queries/{id}Json
{
  "id": "your_knowledge_query_configuration_gid"
}

Step 4

Run a CIQ Execution to trigger the step up advice.

POST https://eu.api.indykite.com/contx-iq/v1/executeJson
{
  "id": "knowledge_query_gid",
  "input_params": {
    "subject_external_id": "alice",
    "token_sub": "alice_user_external_id"
  },
  "page_token": 1
}

CIQ Execution response if the auth_time is too old.

response.jsonJson
{
  "message": "Unauthorized: invalid token. see response Www-Authenticate headers"
}