Tokens & token exchange
OrthID tokens are signed JWTs. They prove who an actor is, what it may do, and - for agents - on whose behalf it acts.
Every authenticated request carries a token. OrthID issues two kinds: a short-lived access token that authorises requests, and a longer-lived refresh token that keeps the session alive. Both are JWTs signed with keys held in your region, so they can be verified anywhere without a network round-trip.
JWT structure and claims
A decoded access token holds the standard registered claims plus the OrthID claims your authorisation logic relies on. The key ones:
sub- the principal id (the actor).typ- the actor type:user,organizationoragent.org- the active organisation.scope- the granted permissions.act- present on agent tokens; records the human the agent acts on behalf of.region,iat,exp- residency and validity window.
{
"iss": "https://au-syd-1.orthid.com",
"sub": "agent_7xQ1vD",
"typ": "agent",
"org": "org_2bT7uX",
"scope": ["records:read", "summaries:write"],
"act": {
"sub": "user_3kP9aZ",
"typ": "user"
},
"region": "au-syd-1",
"iat": 1750595400,
"exp": 1750596000
}Access vs refresh tokens
The two token types do different jobs:
- Access token - sent on every request as a Bearer token, verified with
orthid.sessions.verify. Short lifetime (minutes) so a leaked one expires fast. - Refresh token - never sent to your APIs. The SDK uses it to mint a new access token when the old one expires. Revoking it ends the session immediately.
Token exchange for on-behalf-of agents
Agent delegation uses OAuth 2.0 Token Exchange (RFC 8693). A human’s access token is exchanged for a new, narrower agent token. The agent token carries an act claim that names the human, and its scopecan only be a subset of the human’s. The agent gets exactly what it needs, for a limited time, and nothing more.
import { orthid } from "@orthid/sdk";
// Exchange the human's session for a scoped, expiring agent credential.
const agent = await orthid.agents.issue({
onBehalfOf: "user_3kP9aZ", // the principal human
scope: ["records:read", "summaries:write"],
ttl: "10m",
region: "au-syd-1",
});
// agent.token is a JWT carrying an "act" claim for user_3kP9aZ
console.log(agent.token, agent.expiresAt);Under the hood this is a token-exchange request. The agent token is minted with act.sub set to the human and a scope that the authorisation server has confirmed is allowed for that human:
grant_type=urn:ietf:params:oauth:grant-type:token-exchange &subject_token=<human_access_token> &subject_token_type=urn:ietf:params:oauth:token-type:access_token &requested_token_type=urn:ietf:params:oauth:token-type:access_token &scope=records:read summaries:write &audience=agent_7xQ1vD
How provenance is recorded
The act claim is the chain of accountability. Because it travels inside the signed token, every downstream service sees both the acting agent (sub) and the human it acts for (act.sub) without trusting any extra header. OrthID writes the same pair into the audit log, so orthid.audit.list()can always answer “which agent did this, and on whose behalf?”
Next steps
- The three actors - how agents relate to humans and organisations.
- Scope an agent - a step-by-step guide to issuing and using an agent credential.