Agent infrastructure

Platform integration

Just-in-time tokens for AI agents: a practical guide to issuing short-lived credentials

Just-in-time tokens for AI agents: a seven-step guide to issuing short-lived, scoped credentials with token exchange, DPoP, and per-call authorisation.

7 minute read
Decorative imagery showcasing Pontil's brand

By the end of this guide you'll have a working pattern for issuing just-in-time tokens for AI agents — short-lived, narrowly scoped credentials minted at the moment of a tool call and thrown away after. You'll know how to structure the token exchange, what claims to bind, how to keep the blast radius small, and how to instrument the flow so a security review actually passes.

Prerequisites: an OAuth 2.1 or OIDC-capable identity provider (Auth0, Okta, Entra ID, Keycloak, or your own), an agent runtime that can hold a user session, and a downstream API you control the auth on. Time: about an hour to prototype, a day to production-harden.

This guide assumes you've already decided not to give your agent a long-lived service account. If you're still weighing that trade-off, read our piece on agent identity vs user identity first — this article picks up where that one ends.

Step 1 — Define the token you actually need

Start with the claims, not the crypto. A JIT token for an agent tool call needs to answer four questions at the API boundary: who is the user, what tool is being called, what scope does this specific call need, and when does the token expire.

Write the claim set down before you write any code. A minimal set looks like this:

{
 "sub": "user_9f2a...",
 "act": { "sub": "agent_billing_v3" },
 "scope": "invoices:read invoices:refund",
 "aud": "https://api.internal/billing",
 "exp": 1735689600,
 "jti": "01JABC...",
 "tool_call_id": "tc_01JABC..."
}

The act claim (from RFC 8693) is the one most teams miss. It records that the agent is acting on behalf of the user — not impersonating them. Your audit log needs both identities. Your authorisation logic needs the user's identity. The act claim gives you both without collapsing them.

Step 2 — Pick a token lifetime

Short means short. The point of a JIT token is that if it leaks — from a log line, a prompt injection, a compromised model provider — the window of abuse is measured in seconds.

Sensible defaults:

Call type
Lifetime
Reasoning

Read-only, single call

60 seconds

Long enough for retries, short enough to be useless if leaked

Write, single call

30 seconds

Writes are the ones you'll regret leaking

Long-running (report generation, batch)

5 minutes with refresh

Refresh at the runtime, not the model

Multi-step workflow

Per-step tokens

Never one token for the whole trajectory


Don't reuse tokens across tool calls. The reflex to "cache the token for this session" defeats the point. If your token exchange is slow enough that per-call issuance hurts, fix the exchange — don't lengthen the token.

Step 3 — Wire up the token exchange

Use RFC 8693 token exchange. The agent runtime holds a longer-lived session or access token, and swaps it for a JIT access token at each tool call.

The request:

POST /oauth/token HTTP/1.1
Host: auth.yourdomain.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<user_access_token>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=<agent_credential>
&actor_token_type=urn:ietf:params:oauth:token-type:jwt
&audience=https://api.internal/billing
&scope=invoices:read invoices:refund
&resource=https://api.internal/billing/invoices/inv_123

A note on portability: RFC 8693 registers several subject token types, but IdPs vary in what they'll actually accept. Keycloak's standard token exchange, for example, only accepts access_token as the subject. Check your IdP's docs before you commit to a subject token type — access_token is the safest default.

The response is a short-lived access token bound to that specific audience, scope, and — if your IdP supports it — that specific resource.

Make this call from the tool runtime, not the model. The model should never see the credential. It sees a tool call; the runtime handles the exchange and attaches the token to the outbound API request.

Step 4 — Bind the token to the call

An unbound bearer token is a liability. Anything holding it can use it. Two techniques close that gap.

DPoP (Demonstrating Proof of Possession, RFC 9449) binds the token to a key held by the runtime. The API verifies both the token and a signed DPoP header on each request. Steal the token, you still can't use it.

Resource indicators (RFC 8707) narrow the audience. A token issued for https://api.internal/billing/invoices/inv_123 won't work against https://api.internal/billing/invoices/inv_456. If the agent's next tool call needs a different resource, it gets a different token.

If you can only implement one, pick DPoP. If you can do both, do both. This is the layer where the difference between "we use OAuth" and "we use OAuth properly" actually shows up.

Step 5 — Enforce scope at the API, not the token

A common failure: teams issue a token with scope=invoices:refund and assume the presence of that scope means the call is authorised. It doesn't. The token proves the user has permission to refund invoices. It doesn't prove this specific refund is allowed.

At the API, run three checks in order:

  1. Token validity — signature, expiry, audience, DPoP proof.
  2. Scope — does this endpoint require a scope the token carries?
  3. Semantic authorisation — does the acting user, in their tenant, have permission on this specific resource?

Step three is where most agent security incidents actually happen. The token was fine. The scope was fine. The user just didn't own the invoice they refunded. See API security best practices for AI agents for the fuller boundary model.

Step 6 — Log both identities

Every tool call must produce an audit record that captures the user (sub), the agent (act.sub), the tool, the resource, and the outcome. Two rules:

  • Log the jti and tool_call_id, never the token itself. Tokens in logs are the leading cause of "how did they get in?"
  • Log the decision, not just the request. denied: user does not own resource inv_456 is worth a hundred 200 OKs in an incident.

This is also the record that lets you answer the question every enterprise buyer will eventually ask: when your agent did the thing, was it acting as a real user with real permissions, or as a shared service account with god-mode? JIT tokens are how you get to answer "the former" honestly.

Step 7 — Handle refresh and revocation

Short-lived tokens don't get refreshed — they get re-issued. But the session that authorises re-issuance does need refresh handling.

  • Refresh the user's session token at the runtime, on a schedule the user is aware of.
  • Revoke the session — not just the access token — when the user logs out, changes password, or has their agent access revoked.
  • Publish a revocation event that your runtime subscribes to. Don't wait for the next token exchange to discover the user is gone.

For the mechanics of rotating the signing keys underneath all of this, see webhook signing key rotation — the JWKS overlap pattern is the same.

Common pitfalls

  • Caching the token "for performance". If exchange is slow, fix exchange. Cached JIT tokens are just long-lived tokens with extra steps.
  • Letting the model see the token. The runtime does the exchange. The model calls a tool. If the token appears in a prompt or a trace visible to the model, treat it as leaked and rotate.
  • Skipping the act claim. Without it, your audit log shows the user did something the user didn't do. This is the claim security reviewers ask about specifically.
  • One token for a multi-step trajectory. Each tool call is its own authorisation decision. Issue per call.
  • Trusting scope as authorisation. Scope is coarse. Ownership is fine-grained. You need both checks.
  • Forgetting DPoP or resource indicators. A bearer token with a 30-second lifetime is still a bearer token. Bind it.

Get these seven steps right and the token boundary holds. Get any of them wrong and you've built the same shared-service-account pattern with more moving parts.

Join our weekly newsletter

Stay up to date on the ever changing agentic landscape.

POSTS

Related content

Agent infrastructure

Platform integration

Agent identity vs user identity: the boundary security reviews will demand

5 minute read

Agent infrastructure

API strategy

API security best practices for AI agents: the boundaries that actually hold in production

9 minute read

Agent infrastructure

Platform integration

Agent authentication methods compared: API keys, OAuth 2.1, and delegated access

7 minute read