Skip to main content

Token requests

This page lists the parameters, responses, and errors for each flow on the OAuth server's token endpoint, <publicBaseURL>/oauth/token. Each flow follows the standard linked in its section. This page describes the values Defakto accepts and returns.

Common rules​

  • Requests are POST, with Content-Type: application/x-www-form-urlencoded, and a body of at most 32 KiB.
  • Each parameter appears at most once.
  • A request names the service its token is for with resource or audience, not both.
  • Every request is decided by the trust domain's token policy, after the caller and any presented token are verified.

Client authentication​

In token exchange requests, the workload authenticates as itself, in one of two ways. A request uses one of them.

MethodParametersNotes
JWT-SVID client assertionclient_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-spiffe and client_assertion=<JWT-SVID>Works over any TLS connection, including one that ends at a load balancer in front of the Trust Domain Servers
X.509-SVID over mutual TLSclient_id=<SPIFFE ID>, and the X.509-SVID as the TLS client certificateclient_id must match the SPIFFE ID in the certificate (RFC 8705). The TLS connection must end at the Trust Domain Server, so this method isn't available behind a load balancer that doesn't pass the client certificate through.

When a request has a client assertion, the client assertion is used, and any certificate on the connection is ignored. The SVID must belong to your trust domain.

In token exchange requests, a JWT-SVID client assertion must have its audience set to the trust domain's JWT issuer, not the OAuth issuer.

Workload access token​

A workload exchanges its JWT-SVID for an access token, with the JWT bearer grant (RFC 7523). This is the flow MCP calls workload identity federation. See Call an MCP server from a workload.

ParameterRequiredValue
grant_typeYesurn:ietf:params:oauth:grant-type:jwt-bearer
assertionYesThe workload's JWT-SVID, with its audience set to the OAuth issuer URL
resource or audienceYesThe service the token is for. It becomes the token's aud.
scopeNoThe requested scopes, separated by spaces. Without it, the access token has no scopes, and the response has no scope field.
client_assertion_type and client_assertionNoA JWT-SVID that authenticates the workload, with its audience set to the OAuth issuer URL. When present, the assertion's sub must be the same workload.

The request can also carry a DPoP header, to bind the token to a key. See DPoP.

POST /oauth/token HTTP/1.1
Host: td-server.defakto.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=<JWT-SVID>
&resource=https://helpdesk.example.com/mcp
&scope=tickets.read
{
"access_token": "<access token>",
"token_type": "Bearer",
"expires_in": 300,
"scope": "tickets.read"
}

The access token never outlives the JWT-SVID in assertion. See Lifetimes.

Without a client assertion, the assertion's signature, issuer, and audience are the only checks, so protect JWT-SVIDs as you would any bearer credential.

User sign-in​

An OAuth client, such as an MCP client, gets an access token for a person with the authorization code grant and PKCE, as the MCP authorization specification describes. MCP clients implement this flow already. This section is for people building their own OAuth clients.

Defakto's values for the flow:

  • Client ID. The client's client_id is the URL of its Client ID Metadata Document. The client ID must be listed in oauthClients. Otherwise, the person's browser shows a Defakto error page, with HTTP status 400 and the reference unknown-client, and nothing is sent back to the client. The redirect_uri must be one that the document lists.
  • Authorization endpoint. <publicBaseURL>/oauth/authorize. Defakto sends the person to the identity provider that has login, and the token policy decides the request when the person comes back.
  • Token endpoint. <publicBaseURL>/oauth/token, with grant_type=authorization_code.
  • Audience. Send resource with the service the token is for, in both the authorization request and the token request.
  • PKCE. Required, with the S256 method only. A request without a code_challenge, or with the plain method, is refused.
  • Time limits. The person must finish signing in within 10 minutes of the authorization request. The authorization code is valid for 60 seconds, and can be used once.
  • Access token. The access token lives for 5 minutes, unless the token policy sets a different lifetime, and never outlives the identity provider's ID token from the sign-in.

On-behalf-of token​

A workload exchanges a user's token for an on-behalf-of token, with token exchange (RFC 8693). A workload that received an on-behalf-of token exchanges it again for the next service. See Propagate a user's identity.

ParameterRequiredValue
grant_typeYesurn:ietf:params:oauth:grant-type:token-exchange
subject_tokenYesThe user's ID token or access token from an identity provider, or the on-behalf-of token to re-exchange
subject_token_typeYesurn:ietf:params:oauth:token-type:id_token, urn:ietf:params:oauth:token-type:access_token, or, to re-exchange, urn:defakto:security:token-type:jwt-svid-obo
requested_token_typeYesurn:defakto:security:token-type:jwt-svid-obo
audienceYesThe service the token is for
Client authenticationYesSee Client authentication

A presented ID token or access token must come from a configured identity provider. Its typ header must match the kind of token it's presented as, and a token from an identity provider is refused if its subject is a SPIFFE ID in your trust domain. A presented on-behalf-of token must be one that your trust domain issued.

{
"access_token": "<on-behalf-of token>",
"issued_token_type": "urn:defakto:security:token-type:jwt-svid-obo",
"token_type": "Bearer",
"expires_in": 300
}

The on-behalf-of token never outlives the subject_token. See Lifetimes.

DPoP isn't supported on token exchange. A request with a DPoP header is refused with invalid_dpop_proof. See DPoP.

Transaction Token​

A workload exchanges a token for a Transaction Token, with token exchange.

ParameterRequiredValue
grant_typeYesurn:ietf:params:oauth:grant-type:token-exchange
subject_tokenYesThe user's access token from an identity provider. ID tokens aren't accepted.
subject_token_typeYesurn:ietf:params:oauth:token-type:access_token
requested_token_typeYesurn:ietf:params:oauth:token-type:txn_token
audienceYesYour trust domain name, such as example.com
scopeYesThe scopes of the transaction, separated by spaces. Each must be one of the scopes in the subject_token's scope or scp claim, so a subject_token without one of those claims is refused.
Client authenticationYesSee Client authentication

A Transaction Token can't be exchanged further.

{
"access_token": "<Transaction Token>",
"issued_token_type": "urn:ietf:params:oauth:token-type:txn_token",
"token_type": "N_A",
"expires_in": 300
}

The Transaction Token never outlives the subject_token. See Lifetimes.

The response follows the Transaction Tokens draft, so it never includes a refresh token.

External service token​

A workload gets an access token for a configured external service, with token exchange. The workload acts as itself, so the request presents no subject token. See Get tokens for external services.

ParameterRequiredValue
grant_typeYesurn:ietf:params:oauth:grant-type:token-exchange
audience or resourceYesThe external service's resource value
requested_token_typeNourn:ietf:params:oauth:token-type:access_token, the default
scopeNoThe requested scopes, separated by spaces, within the service's configured grant.clientCredentials.scopes. Without it, Defakto requests all of them.
Client authenticationYesSee Client authentication

The response is a standard OAuth token response with access_token, token_type, and, when the service returns them, expires_in and scope. The access token is the external service's, unchanged. Other fields in the service's response aren't passed on.

Errors​

Errors use the standard OAuth error response, a JSON body with error and error_description. The error_description gives a short reason, such as subject token rejected. Full details, such as which token policy rule refused the request, are recorded on the OAuth server's audit event for the request.

errorCommon causes
invalid_requestA required parameter is missing or repeated, both resource and audience are present, the body exceeds 32 KiB, or the token policy or the external authorizer refused a JWT bearer or token exchange request. In token exchange, also when the subject_token failed to verify, has expired, has the wrong issuer or audience, fails a claimValidationRules rule, or has a typ header for a different kind of token. The error_description is subject token rejected.
invalid_clientThe client assertion failed to verify, or client_id doesn't match the client certificate
invalid_grantIn the JWT bearer grant, the JWT-SVID failed to verify, has expired, or has the wrong audience, or its sub doesn't match the authenticated client. Or the token policy refused the request when the client redeemed an authorization code.
invalid_scopeNone of the requested scopes is allowed
invalid_dpop_proofThe DPoP proof is invalid, or was sent on token exchange. See DPoP.
unsupported_grant_typeThe Trust Domain Server doesn't support the grant
server_errorThe OAuth server couldn't complete the request. For example, an identity provider's keys couldn't be fetched, the external authorizer was unavailable or returned an invalid response, or an external service's authorization server couldn't issue a token

When the token policy refuses a person's sign-in, the OAuth client receives access_denied at its redirect URI, as the authorization code grant defines.