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
Header
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}
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"
}
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
ath
ClaimStart with the raw access token string you received from the Token endpoint.
Compute a SHA-256 hash of the token.
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
ath
{
"htu": "https://id.corppass.gov.sg/authorization-info",
"htm": "POST",
"iat": 1712681904,
"jti": "6ef56d85-e469-4bc3-a508-05d8f94f780e",
"ath": "3rEbqHhURQcbfV3zM9sl1rsBzAcjT9TKqX3akHLZ9Nc"
}
Last updated