Skip to content

Generate a DPoP Proof and Obtain a DPoP-Bound Access Token

This guide walks through generating a DPoP proof JWT and using it to obtain a DPoP-bound access token from Trimble Identity.

For conceptual background on DPoP, cryptographic bindings, and resource-server validation, see Implementing DPoP for Enhanced API Security.


Step 1: Generate an Asymmetric Key Pair

The client application generates an asymmetric cryptographic key pair to be used with DPoP. Supported signing algorithms are specified in the well-known configuration for OpenID Connect (OIDC).

  • The private key is kept secret by the client.
  • The public key will be included in the DPoP proof JWT.
  • A new key pair should be generated for each access token request.

Example: Key Generation (Python)

from cryptography.hazmat.primitives.asymmetric import ec
# Generate EllipticCurve P-256 keys
key_type = "EC"
key_crv = "P-256"
signing_alg = "ES256"
keys = ec.generate_private_key(ec.SECP256R1())
pub = keys.public_key()

Step 2: Create the DPoP Proof JWT

A DPoP proof is a standard JWT with claims and headers relevant to the DPoP flow, signed by the private key generated in Step 1.

The JWT consists of a header and payload with information about the token request. Once created, the JWT is signed by the private key from Step 1.

The DPoP Proof: Structure and Integrity

The DPoP proof is a meticulously structured JSON Web Signature (JWS), signed by the client’s private key.

2.1. JOSE Header: Cryptographic Manifest

The JSON Object Signing and Encryption (JOSE) Header defines the critical cryptographic parameters. It MUST contain the following parameters to ensure proof integrity and identification:

ParameterRequirementBusiness Impact / Validation
typdpop+jwt (Mandatory)Enables immediate server identification and prevents the misuse of other JWT types.
algAsymmetric AlgorithmEnforces true proof-of-possession. Symmetric algorithms are forbidden as they defeat the purpose.
jwkPublic Key in JWK format (Mandatory)The public key is embedded for signature validation. This design choice is critical for self-contained validation, eliminating the need for external key lookups.

Supported asymmetric algorithms include ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512 and EdDSA.

2.2. Payload Claims: Request and Anti-Replay Binding

The payload claims tie the proof to a single, specific HTTP transaction and are central to preventing replay attacks.

ClaimRequirementSecurity Function
jti (JWT ID)Always MandatoryA unique identifier used by the server to detect and prevent replay attacks. (Lifetime: 5 minutes)
htm (HTTP Method)Always MandatoryBinds the proof to the request’s HTTP method (e.g., POST), preventing method reuse.
htu (HTTP URI)Always MandatoryBinds the proof to the request’s URI (excluding query/fragment parts).
iat (Issued At)Always MandatoryTimestamp for “freshness” checks, mitigating delayed replay.
ath (Access Token Hash)ConditionalMandatory only for requests to protected resources. This is the Proof-to-Token binding.

Signature

Once the DPoP JWT is created, the client application signs it with the private key associated with the public key included in the JWT header (jwk claim).

Example: Creating a DPoP Proof JWT (Python)

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
import base64
import datetime
import json
import jwt
import math
import uuid
TID_MTLS_TOKEN_ENDPOINT = "https://mtls.stage.id.trimblecloud.com/oauth/token"
# Key generation (repeated from Step 1)
key_type = "EC"
key_crv = "P-256"
signing_alg = "ES256"
keys = ec.generate_private_key(ec.SECP256R1())
pub = keys.public_key()
## Encode public key as required for JWK
## (JWK specs: https://datatracker.ietf.org/doc/html/rfc7517)
# url-safe-base64-padding-removed encoded 'x' value - BIG-ENDIAN
ec_x_val = pub.public_numbers().x.to_bytes(math.ceil(pub.key_size / 8), 'big')
b64_x_val = base64.urlsafe_b64encode(ec_x_val).decode('ascii').rstrip('=')
# url-safe-base64-padding-removed encoded 'y' value - BIG-ENDIAN
ec_y_val = pub.public_numbers().y.to_bytes(math.ceil(pub.key_size / 8), 'big')
b64_y_val = base64.urlsafe_b64encode(ec_y_val).decode('ascii').rstrip('=')
ec_key_id = uuid.uuid4().hex
# Construct JWK
jwk_headers = {
"kty": key_type,
"x": b64_x_val,
"y": b64_y_val,
"crv": key_crv,
"kid": ec_key_id
}
## Construct JWT
jwt_id = uuid.uuid4().hex
now_timestamp = int(datetime.datetime.now().timestamp())
jwt_headers = {
"typ": "dpop+jwt",
"jwk": jwk_headers
}
jwt_payload = {
"jti": jwt_id,
"iat": now_timestamp,
"htm": "POST",
"htu": TID_MTLS_TOKEN_ENDPOINT
}
private_key_pem = keys.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
# Sign JWT
encoded_jwt = jwt.encode(
jwt_payload, private_key_pem, algorithm=signing_alg, headers=jwt_headers
)

Example: Decoded DPoP JWT

Header

{
"alg": "ES256",
"jwk": {
"crv": "P-256",
"kty": "EC",
"x": "7S0WsYMekxDTCRYK_e-jkH4_ba01LcfFT0PmbMjm6Z-lla4QZap2cOmTtGx5S1_f",
"y": "rjJIxzHusvMNEJR4btr09CY_cOafNpZwn1PbqLfIa8BAJPg-HaQ66Gssijx_ErlT"
},
"typ": "dpop+jwt"
}

Payload

{
"jti": "aa2af8c5-da36-4b47-9397-9f539e93470d",
"htm": "POST",
"htu": "https://mtls.stage.id.trimblecloud.com/oauth/token",
"iat": 1768839464
}

Step 3: Request a DPoP-Bound Access Token from Trimble Identity

The request is sent to the mTLS version of the /token endpoint.

  • Endpoint: POST <baseurl>/oauth/token
  • Authentication: mTLS for client authentication.

Headers

HeaderValue
DPoP<DPoP Proof JWT> (created in Step 2)

Supported grant types

Grant TypeUse case
urn:ietf:params:oauth:grant-type:subject-delegation
urn:ietf:params:oauth:grant-type:token-exchange
refresh_token

Step 4: Trimble Identity Validates the DPoP Proof and Returns an Access Token

Trimble Identity receives the request and:

  1. Verifies the DPoP proof JWT signature using the public key provided in the JWT.
  2. Validates the htm, htu, jti, and iat claims.

If the request is valid, the server creates an access token bound to the public key provided in the JWT.

Access Token Claims

ClaimDescription
cnfValues that bind the access token to the client that requested it (sender-constrains the token).
cnf.x5t#S256The SHA-256 thumbprint of the X.509 client certificate (mTLS certificate) used to make the request.
cnf.jktThe hash of the public key provided in the DPoP proof.

Claims relevant to DPoP are listed above. See JWT Definitions for details on additional claims found in the access token.


Step 5: Generate a New DPoP Proof JWT for the Resource Request

Every API request made with a DPoP-bound access token must include a new DPoP proof. This is similar to creating the DPoP proof in Step 2, but with different values.

Using the same keys created in Step 1, generate a new DPoP proof JWT. The payload claims htm and htu must be updated to reflect the API endpoint being called, and a new claim, ath, is included in the payload. The JWT is then signed with the client’s private key.

Additional JWT Payload Claim

ClaimDescription
athBase64url-encoded SHA-256 hash of the access token. Binds the proof to the specific token.

Step 6: Submit a Request to the Resource Server (via API Cloud)

The request is sent to the resource server through API Cloud.

  • Endpoint: Endpoint of the resource server.

Headers

HeaderValue
DPoP<DPoP Proof JWT> (created in Step 5)
AuthorizationDPoP <access_token>

Request Body

As required by the endpoint.

Example Request

GET /executions
Host: https://cloud.stage.api.trimblecloud.com/Processing/api/1
Content-Type: application/x-www-form-urlencoded
DPoP: <DPoP Proof>
Authorization: DPoP <access token>

Example Access Token Response

{
"token_type": "DPoP",
"expires_in": 3600,
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjIi... <truncated for brevity>",
"issued_token_type": "urn:ietf:params:oauth:token-type:jwt"
}

Example Decoded Access Token

Header

{
"alg": "RS256",
"kid": "2",
"typ": "JWT"
}

Payload

{
"iss": "https://stage.id.trimblecloud.com",
"exp": 1768843090,
"nbf": 1768839490,
"iat": 1768839490,
"jti": "35e9a6e6ea7649bab18e4f3e816bfa8a",
"jwt_ver": 2,
"sub": "73eb11b5-87f3-4253-ad93-368a2acb4128",
"identity_type": "user",
"amr": [
"client_credentials",
"subject_delegated_access"
],
"azp": "6783952d-12f0-4257-ac26-ce16355736c2",
"auth_time": 1768839490,
"cnf": {
"x5t#S256": "f3fe143b5c070763c067f114dd66ae089683e94882a3a5c79f6218f465ef5994",
"jkt": "dHU9tQDHNagHvFFHEEZFyUv1eGMNiTsYZzTP_Cvd2wc"
},
"data_region": "us",
"account_id": "3983a7bc-beaa-54ba-8d7e-ee640c082f3a",
"aud": [
"695d741b-2f4a-4cb8-8d97-e81fd4dbd765"
],
"scope": "iam",
"act": {
"sub": "6783952d-12f0-4257-ac26-ce16355736c2"
}
}

Step 7: API Cloud and Resource Server Validate the DPoP Proof

API Cloud receives the request and validates the DPoP proof and access token. If valid, API Cloud forwards the request to the resource server, which performs the same validation:

  1. Verifies the DPoP proof JWT signature using the public key provided in the JWT (jwk claim).
  2. Validates the htm, htu, jti, iat, and ath claims.
  3. Verifies that the public key in the DPoP proof JWT (jwk claim) matches the public key hash in the access token (cnf.jkt claim).

For full details on cryptographic bindings, the complete validation protocol, and error handling, see Implementing DPoP — RS Validation Protocol.