Demonstrating Proof of Possession (DPoP)

This security mechanism is defined in RFC 9449 and enhances OAuth 2.0 by binding an access token to a specific client instance and request. It prevents intercepted tokens from being reused by attackers (replay attacks), especially useful for public clients like SPAs or mobile apps.

The client must generate a signed JWT (DPoP proof) for each request to the Token endpoint or protected resources. This JWT proves that the client holds the private key it claims to, and that it is initiating the request itself.

DPoP Header Example

DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6IkVDIiwieCI6IlBFZHFyLTFSemZ2QkZsazE0U085UkZSSm1uV2FGNlc3YlZPZ2Y3Wk9KUVkiLCJ5IjoieGVBeklzcjlTbmk1RWVnVE5xZHFZblg1V08yTm5wTHBOTXFHTEoxSjRDWSIsImNydiI6IlAtMjU2In19.eyJpYXQiOjE3NDQxODQ5ODgsImp0aSI6IjVlNmU1NTA5LTcwZTMtNGJiNy1hZDMwLTRlMTc2ZmZkM2NhOCIsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL2lkLmNvcnBwYXNzLmdvdi5zZy9tZ2Evc3BzL29hdXRoL29hdXRoMjAvdG9rZW4ifQ.fggiYkc6lBwIKgpZDJjSA2c3_vkrxWHxJ_7WN9RMpyqjfgOCB1Cwlvdcw7AsGNF1nDsi59s8DHUHnM6a9warbA

Decoded DPoP JWT

{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "...",
    "y": "..."
  }
}
Field
Description

typ

Type of token. Always set to "dpop+jwt".

alg

Signing algorithm. The supported values are ES256, ES256K, ES384, ES512.

jwk

The public key corresponding to the private key used to sign the JWT. Must match the key used in the DPoP-bound token.

Payload

{
  "htm": "POST",
  "htu": "https://id.corppass.gov.sg/mga/sps/oauth/oauth20/token",
  "iat": 1744184988,
  "jti": "5e6e5509-70e3-4bb7-ad30-4e176ffd3ca8",
  "ath": "hKzf0cQEXAMPLEUXxKXJvHr_F34Pp7pg6LFaP_LZ7Jw"
}
Claim
Required
Description

htm

Yes

The HTTP method of the request (e.g., POST)

htu

Yes

The full HTTP URI of the request (e.g., https://id.corppass.gov.sg/mga/sps/oauth/oauth20/token).

iat

Yes

Issued-at time (epoch timestamp). Used to detect replayed proofs.

jti

Yes

A unique identifier for the proof. Prevents replay attacks. Must be unique for each request.

ath

No

Required only when accessing protected resource endpoints (e.g., /authorization-info, /entity-info).

This claim contains the base64url-encoded SHA-256 hash of the DPoP-bound access token and is used to bind the proof to that specific token.

Access Token Hash (ath)

The ath (Access Token Hash) claim is a mandatory field in the DPoP proof JWT when calling protected endpoints such as /authorization-info and /entity-info. It cryptographically binds the proof to the access token being used in a request, preventing replay attacks and misuse of tokens.

How to Compute the ath Claim

  1. Start with the raw access token string you received from the Token endpoint.

  2. Compute a SHA-256 hash of the token.

  3. Base64URL-encode the hash (no padding).

Example (Pseudo-code)

const crypto = require("crypto");

function computeATH(accessToken) {
  const accessToken = "eyJhbGciOi...";
  const ath = Buffer.from(crypto.createHash("sha256")
           .update(accessToken).digest()
           ).toString("base64url");
           
  return ath;
}

Example Output

"ath": "3rEbqHhURQcbfV3zM9sl1rsBzAcjT9TKqX3akHLZ9Nc"

Example Payload with ath

{
  "htu": "https://id.corppass.gov.sg/authorization-info",
  "htm": "POST",
  "iat": 1712681904,
  "jti": "6ef56d85-e469-4bc3-a508-05d8f94f780e",
  "ath": "3rEbqHhURQcbfV3zM9sl1rsBzAcjT9TKqX3akHLZ9Nc"
}

Last updated