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

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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"id": "your_policy_configuration_gid"
}Step 3
Create a CIQ Query in the context of the policy to configure the step up advice.
{
"nodes": [
"company.external_id",
"payment.external_id"
]
}Request to create a CIQ Query configuration using REST.
{
"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.
{
"id": "your_knowledge_query_configuration_gid"
}Step 4
Run a CIQ Execution to trigger the step up advice.
{
"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.
{
"message": "Unauthorized: invalid token. see response Www-Authenticate headers"
}CIQ Execution response if the iat is less than an hour ago.
{
"data": [
{
"nodes": {
"company.external_id": "companyParking",
"payment.external_id": "cb123"
}
}
]
}API Endpoints
/capture/v1/nodes /capture/v1/relationships /configs/v1/authorization-policies /configs/v1/knowledge-queries /contx-iq/v1/execute