Back to all resources
ContX IQ ContX IQ Json

ContX IQ: Step-Up Authentication Based on Token Issue Time (iat Claim)

Enforce token freshness by checking the iat (issued at) claim. If the token was issued more than one hour ago, return an advice requesting a new token. Different from auth_time - this checks token age, not authentication age.

ContX IQ: Step-Up Authentication Based on Token Issue Time (iat Claim)

This example enforces token freshness using the iat (issued at) JWT claim:

What is iat?

The iat claim indicates when the token was issued/created.

Updated on every token refresh (unlike auth_time which tracks actual authentication).

Difference from auth_time (ciq-12):

- iat: When was this specific token created?

- auth_time: When did the user last enter their credentials?

Policy logic:

- Calculate token age: now() - iat

- If > 1 hour: Return advice "Get a fresh token"

- If <= 1 hour: Allow the operation

Use case: Ensure tokens are recent, preventing use of old/potentially compromised tokens.

Use case

Scenario: API requires tokens issued within the last hour for all operations.

Token claims:

- iat: 2024-01-15T09:00:00Z (when token was created)

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

- Token age: 1.5 hours (exceeds 1 hour limit)

Request flow:

1. User makes request with valid but old token

2. Policy checks: iat > 1 hour ago?

3. Response: {decision: false, advice: {action: "refresh_token", reason: "token_too_old"}}

4. Application uses refresh token to get new access token (new iat)

5. Same request now succeeds with fresh token

Compare with ciq-12:

- ciq-12 (auth_time): User must re-enter password

- ciq-13 (iat): Application can silently refresh token

Use iat for: General token freshness

Use auth_time for: Sensitive operations requiring active user presence

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 iat claim (standard JWT claim)

- Token Introspect configuration: Extracts iat claim automatically

JWT iat claim:

- Standard JWT claim present in virtually all tokens

- Updates every time a new token is issued (including refreshes)

- Different from auth_time which only updates on actual authentication

Steps

Step 1: Ingest Graph Data

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

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

- Result: Graph ready for operations

Step 2: Create Policy with iat Freshness Check

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST policy with conditions:

- Calculate token age from iat claim

- If token age > 3600 seconds (1 hour): Return refresh advice

- If token age <= 3600 seconds: Allow operation

- Result: Policy ID returned

Step 3: Create Query with Token Freshness Requirement

- Authentication: ServiceAccount credential (Bearer token)

- Action: POST query that will check token freshness before executing

- Result: Query ID returned

Step 4: Execute with Old Token

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

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

- Result if iat too old: {decision: false, advice: {refresh_token: true}}

- Result if iat recent: Operation succeeds

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 iat claim is more than an hour ago.

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.iat",
      "value": "$one_hour_ago",
      "advice": {
        "error": "insufficient_user_authentication",
        "error_description": "More recent authentication is required, token > 1 hour ago"
      }
    }
  },
  "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.iat\",\"value\":\"$one_hour_ago\",\"advice\":{\"error\":\"insufficient_user_authentication\",\"error_description\":\"More recent authentication is required, token > 1 hour ago\"}}},\"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",
    "one_hour_ago": 1775486549
  },
  "page_token": 1
}

CIQ Execution response if the iat is too old.

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

CIQ Execution response if the iat is less than an hour ago.

response.jsonJson
{
  "data": [
    {
      "nodes": {
        "company.external_id": "companyParking",
        "payment.external_id": "cb123"
      }
    }
  ]
}