Tokens
This page describes the tokens the OAuth server issues, how a receiving service validates each one, and how long each one lives.
| Token | Format | Signed with |
|---|---|---|
| Access token | A JWT access token (RFC 9068), typ at+jwt | The OAuth signing key |
| On-behalf-of token | A JWT-SVID with an obo claim | The JWT-SVID signing key |
| Transaction Token | A Transaction Token, typ txntoken+jwt | The OAuth signing key |
| External service token | Whatever the external service issues | The external service's key |
Access tokens
Access tokens are for OAuth resource servers, such as MCP servers. Defakto issues them to workloads, with the JWT bearer grant, and to OAuth clients, for people who sign in.
| Claim | Value |
|---|---|
iss | The OAuth issuer |
sub | For a workload, its SPIFFE ID. For a person, their identifier from the identity provider, which is never a SPIFFE ID. |
aud | The resource or audience the caller requested |
client_id | For a workload, its SPIFFE ID, the same as sub. For a person, the OAuth client's client ID. |
scope | The granted scopes |
cnf | Only on a DPoP-bound token: the thumbprint of the bound key, as {"jkt": "<thumbprint>"} |
jti, iat, exp | The token's ID, issue time, and expiry |
For examples of an access token for a workload and for a person, see What your MCP server receives.
Validating an access token
Validate access tokens as RFC 9068 describes, with these values:
- Issuer: the OAuth issuer, not the JWT-SVID issuer.
- Keys: the keys at your trust domain's OAuth JWKS Endpoint, which the OAuth server's metadata also lists as
jwks_uri. - Audience: your service's identifier, such as your MCP server's URL.
A token with a cnf claim is bound to a key with DPoP. See DPoP.
On-behalf-of tokens
An on-behalf-of token is a JWT-SVID for the acting workload, with an obo claim that names the user and every workload that acted for the user. It's signed with the trust domain's JWT-SVID key, so it validates with the same SPIFFE libraries and keys as any JWT-SVID. A JWT-SVID without an obo claim is an ordinary JWT-SVID.
{
"iss": "<JWT-SVID issuer>",
"sub": "spiffe://example.com/ns/orders/sa/api",
"aud": "spiffe://example.com/payments",
"exp": 1792598887,
"iat": 1792598587,
"jti": "de0043b7455d66690dbe37ff506d2ef3",
"txn": "b7814530c95811d288ac0390515c61af",
"obo": {
"iss": "https://idp.example.com",
"sub": "alice-8f3k2",
"sub_profile": "user",
"via": "idToken",
"act": {
"iss": "spiffe://example.com",
"sub": "spiffe://example.com/ns/orders/sa/api",
"act": {
"iss": "spiffe://example.com",
"sub": "spiffe://example.com/ns/web/sa/backend"
}
}
}
}
| Claim | Value |
|---|---|
sub | The acting workload's SPIFFE ID |
aud | The service the token is for |
txn | A transaction identifier (RFC 8417), created at the first exchange and carried unchanged through every re-exchange |
obo.iss | The issuer of the identity provider that vouched for the user |
obo.sub | The user's identifier, from the sub claim of the user's token. See User identifier. |
obo.sub_profile | user |
obo.via | How the user arrived: idToken, accessToken, or login. Later exchanges match the token policy's user the same way. |
obo.act | The workloads that acted for the user, most recent first, nested as in RFC 8693 section 4.1. The outermost act.sub is always the same as sub. |
Custom claims from the token policy appear at the top level.
Validating an on-behalf-of token
- Validate it as a JWT-SVID, with a SPIFFE library or as the JWT-SVID specification describes.
- Read the user from
obo.sub, and the identity provider fromobo.iss. If you expect users from particular identity providers only, checkobo.iss. - Read
sub, or the outermostobo.act.sub, for the workload calling you, and the nestedactentries for the workloads before it.
Like any JWT-SVID, an on-behalf-of token is a bearer token. It names the workload it was issued to, but doesn't prove which workload sent it.
Transaction Tokens
A Transaction Token records who started a transaction, and travels with every call in the transaction in the Txn-Token HTTP header. See Propagate a user's identity.
| Claim | Value |
|---|---|
sub | The user or workload who started the transaction |
aud | Your trust domain name |
txn | A transaction identifier |
scope | The transaction's scopes |
req_wl | The SPIFFE ID of the workload that requested the Transaction Token |
iat, exp | The token's issue time and expiry |
Validating a Transaction Token
Validate Transaction Tokens as the Transaction Tokens draft describes, with these values:
- Keys: the keys at your trust domain's OAuth JWKS Endpoint.
- Audience: your trust domain name.
External service tokens
Defakto returns an external service's token exactly as the service's authorization server issued it. The service sets its format, contents, and lifetime, and validates it. Defakto never accepts an external service token back as a token to exchange.
Lifetimes
A token that Defakto issues lives for 5 minutes, unless a matching token policy rule's maxTTL sets a different lifetime, longer or shorter. Each kind of token has a maximum lifetime. A longer maxTTL is capped at that maximum:
| Token | Maximum lifetime |
|---|---|
| Access token for a workload | 7 days |
| Access token for a person | 7 days |
| On-behalf-of token | 24 hours |
| Transaction Token | 10 minutes |
Every token is also capped by:
- The remaining lifetime of the credential it was issued from. An access token never outlives the JWT-SVID that the workload presented, or the identity provider's ID token from a sign-in. An on-behalf-of token never outlives the user's token or the on-behalf-of token it was exchanged from. A Transaction Token never outlives the user's token it was exchanged from.
- A shorter
max_ttlfrom an external authorizer, when one decides the request.
Client authentication doesn't affect the lifetime. A JWT-SVID client assertion, or the X.509-SVID used for mutual TLS, only proves who the caller is. The expiry of that credential doesn't shorten the issued token.