Skip to main content
The token endpoint implements OAuth 2.0 and token exchange flows to convert authorization codes or verifiable presentations into JWT access tokens. It supports multiple grant types for different authentication scenarios.

POST /token

Exchange credentials for a JWT access token.

Request

grant_type
string
required
The type of grant being requested. Determines which authentication flow is used.Supported values:
  • authorization_code - Exchange authorization code for token (standard OAuth 2.0)
  • vp_token - Direct VP token exchange (OIDC4VP extension)
  • urn:ietf:params:oauth:grant-type:token-exchange - Token exchange flow (RFC 8693)

Authorization Code Flow

code
string
required
The authorization code received from the authentication response.Example: IwMTgvY3JlZGVudGlhbHMv
redirect_uri
string
required
Must match the redirect URI provided in the original authorization request.Format: URIExample: https://my-portal.com/auth_callback

VP Token Flow

vp_token
string
required
The verifiable presentation token containing verified credentials. Used when grant_type is vp_token.Can be either:
  • JSON-LD Verifiable Presentation (Base64-URL encoded)
  • SD-JWT VC format
scope
string
Space-delimited list of requested scopes for the access token. If not provided, the default scope for the client is used.Example: openid tir_read tir_write

Token Exchange Flow

subject_token
string
required
A security token representing the identity of the party. For OIDC4VP, this contains the VP token.Required when grant_type is urn:ietf:params:oauth:grant-type:token-exchange.
subject_token_type
string
required
An identifier indicating the type of security token in the subject_token parameter.Supported value: urn:eu:oidf:vp_token
resource
string
required
A URI indicating the target service or resource where the client intends to use the token.If not provided as a path parameter (e.g., /services/{service_id}/token), this field is required.Format: URIExample: https://api.example.org/service
audience
string
The logical name of the target service where the client intends to use the token.Example: packet-delivery-service
requested_token_type
string
An identifier for the type of requested security token.Supported value: urn:ietf:params:oauth:token-type:access_token

Response

token_type
string
The type of token returned. Always Bearer for this implementation.
access_token
string
The JWT access token containing the verified credential claims.The token is signed using the verifier’s private key and can be verified using the public keys from the /.well-known/jwks endpoint.
expires_in
number
Token lifetime in seconds.Example: 3600 (1 hour)
issued_token_type
string
The type of token that was issued. Present for token exchange flows.Value: urn:ietf:params:oauth:token-type:access_token
scope
string
The scopes associated with the issued token. Present for VP token and token exchange flows.Example: openid tir_read

Examples

Authorization Code Flow

curl -X 'POST' \
  'https://verifier.example.org/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=authorization_code&code=IwMTgvY3JlZGVudGlhbHMv&redirect_uri=https%3A%2F%2Fmy-portal.com%2Fauth_callback'
Response:
200 OK
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6IldPSEZ1NEhaNTlTTTg1M0M3ZU4wT3ZsS0dyTWVlckRDcEhPVVJvVFF3SHciLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiJkaWQ6a2V5Ono2TWtzOW05aWZMd3kzSldxSDRjNTdFYkJRVlMyU3BSQ2pmYTc5d0hiNXZXTTZ2aCIsImlzcyI6Imh0dHBzOi8vdmVyaWZpZXIuZXhhbXBsZS5vcmciLCJleHAiOjE3MTAwMDAwMDAsInZjIjp7fX0.signature"
}

VP Token Direct Exchange

curl -X 'POST' \
  'https://verifier.example.org/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=vp_token&vp_token=eyJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXS4uLg&scope=openid%20tir_read'
Response:
200 OK
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6IldPSEZ1NEhaNTlTTTg1M0M3ZU4wT3ZsS0dyTWVlckRDcEhPVVJvVFF3SHciLCJ0eXAiOiJKV1QifQ...",
  "scope": "openid tir_read",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}

Token Exchange Flow (RFC 8693)

curl -X 'POST' \
  'https://verifier.example.org/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token=eyJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXS4uLg&subject_token_type=urn:eu:oidf:vp_token&resource=https://api.example.org/service&scope=tir_read'
Response:
200 OK
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6IldPSEZ1NEhaNTlTTTg1M0M3ZU4wT3ZsS0dyTWVlckRDcEhPVVJvVFF3SHciLCJ0eXAiOiJKV1QifQ...",
  "scope": "tir_read",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}

Service-Specific Token Endpoint

POST /services//token

Service-specific version of the token endpoint. The service context is provided via the path parameter instead of the request body.
service_id
string
required
The identifier of the client/service requesting the token. This determines the scope and trust configuration.Example: packet-delivery-portal
All other parameters and behavior are identical to the main /token endpoint.

Example

curl -X 'POST' \
  'https://verifier.example.org/services/packet-delivery-portal/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=authorization_code&code=IwMTgvY3JlZGVudGlhbHMv&redirect_uri=https%3A%2F%2Fmy-portal.com%2Fauth_callback'

JWT Access Token Structure

The issued JWT access token contains the verified credential data and can be used for authorization in downstream services.

Token Header

{
  "alg": "ES256",
  "kid": "WOHFu4HZ59SM853C7eN0OvlKGrMeerDCpHOURoTQwHw",
  "typ": "JWT"
}
alg
string
The signing algorithm used. Configured via keyAlgorithm setting.Supported algorithms: RS256, ES256Default: RS256
kid
string
Key identifier for the signing key. Used to retrieve the public key from the JWKS endpoint.
typ
string
Token type. Always JWT.

Token Payload

{
  "iss": "https://verifier.example.org",
  "sub": "did:key:z6Mks9m9ifLwy3JWqH4c57EbBQVS2SpRCjfa79wHb5vWM6vh",
  "aud": "packet-delivery-service",
  "exp": 1710000000,
  "iat": 1709996400,
  "nbf": 1709996400,
  "scope": ["openid", "tir_read"],
  "verifiableCredential": {
    "type": ["VerifiableCredential", "PacketDeliveryService"],
    "credentialSubject": {
      "id": "did:web:dome-marketplace.org",
      "firstName": "HappyPets",
      "roles": [{
        "names": ["GOLD_CUSTOMER", "STANDARD_CUSTOMER"],
        "target": "did:key:z6MksU6tMfbaEzvaRe5oFE4eZTVTU4HJM4fmQWWgsDGQVser"
      }],
      "familyName": "Prime",
      "email": "[email protected]"
    }
  }
}
iss
string
The issuer of the JWT (the verifier instance).
sub
string
The subject of the token (holder DID from the credential).
aud
string
The intended audience for the token.
exp
number
Token expiration time (Unix timestamp).
verifiableCredential
object
The first verified credential from the presentation. Contains the full credential data including the credential subject and all claims.
Current Limitation: When multiple credentials are submitted in the VP token, all are verified but only the first credential is included in the generated JWT. Future versions may support multiple credentials.

Error Responses

summary
string
Brief error description.
details
string
Detailed error information.

Common Errors

400 - Missing Grant Type
{
  "summary": "Missing Input",
  "details": "Expected 'grant_type' in the request body."
}
400 - Unsupported Grant Type
{
  "summary": "Unsupported Grant Type",
  "details": "The provided grant_type is not supported. Use 'authorization_code', 'vp_token', or 'urn:ietf:params:oauth:grant-type:token-exchange'."
}
400 - Missing Code
{
  "summary": "Missing Input",
  "details": "Expected 'code' parameter for authorization_code grant."
}
400 - Invalid Token Request
{
  "summary": "Invalid Request",
  "details": "Missing required parameter 'redirect_uri'."
}
400 - Missing Resource
{
  "summary": "Missing Input",
  "details": "Expected 'resource' parameter for token exchange."
}
400 - Invalid Subject Token Type
{
  "summary": "Invalid Subject Token Type",
  "details": "Expected subject_token_type to be 'urn:eu:oidf:vp_token'."
}
403 - Verification Failed
{
  "summary": "Verification Failed",
  "details": "Unable to verify the provided credential against the trusted issuers registry."
}

Verifying the JWT Token

Downstream services can verify the JWT token using the verifier’s public keys:
  1. Retrieve Public Keys
    curl https://verifier.example.org/.well-known/jwks
    
  2. Verify Signature Use a JWT library to verify the token signature using the public key matching the kid in the token header.
  3. Validate Claims
    • Check exp (expiration)
    • Verify iss (issuer)
    • Validate aud (audience) matches your service
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: 'https://verifier.example.org/.well-known/jwks'
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, function(err, key) {
    const signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

jwt.verify(token, getKey, {
  audience: 'my-service',
  issuer: 'https://verifier.example.org'
}, (err, decoded) => {
  if (err) {
    console.error('Token verification failed:', err);
  } else {
    console.log('Token verified:', decoded);
  }
});

Build docs developers (and LLMs) love