Skip to main content

Token & Session Boundary

Blueprint · ← Policy contracts · Token & session · PEP enforcement →

The bearer token and claims live in the agentic app session. They attach to every Agentic App → PEP → downstream call. They never cross the LLM boundary.

THE CLAIM

If roles, entitlements, or tokens appear in the LLM payload, you have prompt governance, not PGAR.

What the agentic app holds

Agentic app session (JSON)
{
"session_id": "sess-8f2a",
"token": "eyJhbGciOiJSUzI1NiIs...",
"claims": {
"sub": "officer-123",
"roles": ["corporate_banking_officer"],
"emts": { "payments.lookup": true },
"limits": { "wire.auto_approved": 25000 }
}
}
About claims (and limits in particular)

sub and roles align with common OIDC/JWT practice. emts is entitlements shorthand in this series (production systems often use OAuth scopes, app roles, or custom IdP claims instead).

Numeric limits in the token are less universal. In banking they usually live in:

  • A limits engine or core banking service
  • PDP context assembled at decision time (the PEP enriches SARAC before calling the PDP)
  • Risk or policy stores, not the IdP

Some teams put custom numeric claims in tokens via Keycloak or Azure mappers for simple cases. Many architects avoid limits in JWTs because they change often, are hard to revoke quickly, and bloat the token.

So limits: { "wire.auto_approved": 25000 } in this example means policy-relevant attributes the PDP may use, not "every bank embeds wire caps in OIDC." The agentic app session may hold them after ingress, or the PEP may fetch them at enforcement time and pass them in SARAC context. Either way, they stay out of the LLM payload.

See Policy contracts for how subject and context map to SARAC.

What the LLM sees

LLM request payload (JSON)
{
"messages": [{ "role": "user", "content": "Send wire to Acme for INV-8842" }],
"tools": [
{ "name": "lookup_beneficiary", "parameters": { "payee_name": "string" } },
{ "name": "initiate_wire", "parameters": { "amount": "number" } }
]
}

No Authorization header. No roles. No limits. No policy text.

The PGAR test (automate this)

CheckPass criteria
LLM request scanZero token patterns, zero roles/emts/limits keys
Tool result hygieneDownstream secrets stripped before model sees results
Session bindingEvery PEP call uses same sub as ingress token
Env injectionNo credentials in model env or system prompt

Failure classes

  • Token in context: model prompt includes bearer token or API key
  • Entitlements in system prompt: "you may not exceed $25k" instead of PDP
  • Session bleed: concurrent users share orchestration state
  • Claim staleness: long agent loop uses expired token without refresh

Implementation checklist

  1. Ingress validates token once; agentic app stores session reference
  2. LLM adapter strips all auth fields before API call
  3. PEP receives proposal + token + claims from app only
  4. Tool results sanitized (no raw downstream auth headers)
  5. Token refresh on TTL boundary for multi-step flows

Resume and restart

The run pin on the agentic app is the contract. The token is not a field you copy into conversation Redis.

On resumeDo
Access token still validRe-attach from the token store; same sub as ingress
Token expired mid-loopRefresh or re-mint from IdP; bind the new token to the same run_id
Pod restartLoad pin from the run store, then re-bind token as above

Do not persist the raw bearer next to chat turns. Do not send a stale token to PEP. Session bleed and claim staleness stay failure classes.

How the run record survives a restart: Session custody.

Trace fields

session_id, sub, token_iat, token_exp, llm_payload_hash, credential_leak_scan

See: Session custody · Boundary: Agentic app · Boundary: LLM proposal