# Demonstrating Proof of Possession (DPoP)

Demonstrating Proof of Possession (DPoP) in OAuth 2.0 ensures that authorization codes and access tokens can only be used by the original client to which they were issued for.

It prevents intercepted tokens/codes from being reused by attackers (replay attacks), which is especially useful for public clients like SPAs or mobile apps.&#x20;

This security mechanism is defined in [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449).

## How it works

The DPoP mechanism enables a client to prove possession of a private key when obtaining and using both authorization codes and access tokens in a FAPI 2.0 flow.

At the start of each authorization request, the client generates an ephemeral signing key pair. Using this key pair, the client creates a **signed JWT (the DPoP Proof JWT)** and sends it in the DPoP header to the authorization server. Presenting this proof demonstrates that the client holds the private key it claims to and that it is initiating the request directly.

The authorization server binds the client’s presented public key to the resulting credential.

* For an authorization code grant, the server binds the key to the issued authorization code, ensuring that only the client holding the private key can redeem it at the token endpoint.
* For an access token, the server embeds the client’s public key into the token, restricting its use to the legitimate client with the private key.

When the client later redeems the authorization code or invokes protected resource endpoints with an access token, it must again present a new DPoP proof JWT alongside its credential. The server verifies the proof and ensures that the bound credential is only usable by the same client instance that created the original request.

{% hint style="warning" %}
Corppass expects the DPoP proof to be provided for requests made to the [**Pushed Authorization Request**](https://docs.corppass.gov.sg/corppass-authorization-api-fapi-2.0/integration-guide/2.-authorization-endpoint#step-1-sending-the-pushed-authorization-request) **endpoint,** [**Token**](https://docs.corppass.gov.sg/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/3.-token-endpoint) **endpoint** and other **protected endpoints** (eg. [Userinfo](https://docs.corppass.gov.sg/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/4.-userinfo-endpoint) endpoint `/userinfo` ).
{% endhint %}

## Generating a DPoP Proof JWT

First, clients need to generate an ephemeral signing key pair. Currently, Corppass only supports EC keys.

{% hint style="warning" %}
Use the same ephemeral key pair for all subsequent requests stemming from the same authentication request.
{% endhint %}

Next, clients need to construct the signed JWT as per the following specifications:

### DPoP Request Header

The DPoP proof JWT should be base64-encoded and sent with the `DPoP` request header.

{% code overflow="wrap" %}

```http
DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6IkVDIiwieCI6IlBFZHFyLTFSemZ2QkZsazE0U085UkZSSm1uV2FGNlc3YlZPZ2Y3Wk9KUVkiLCJ5IjoieGVBeklzcjlTbmk1RWVnVE5xZHFZblg1V08yTm5wTHBOTXFHTEoxSjRDWSIsImNydiI6IlAtMjU2In19.eyJpYXQiOjE3NDQxODQ5ODgsImp0aSI6IjVlNmU1NTA5LTcwZTMtNGJiNy1hZDMwLTRlMTc2ZmZkM2NhOCIsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL2lkLmNvcnBwYXNzLmdvdi5zZy9tZ2Evc3BzL29hdXRoL29hdXRoMjAvdG9rZW4ifQ.fggiYkc6lBwIKgpZDJjSA2c3_vkrxWHxJ_7WN9RMpyqjfgOCB1Cwlvdcw7AsGNF1nDsi59s8DHUHnM6a9warbA
```

{% endcode %}

### DPoP JWT Header Structure

Example of a DPoP proof JWT header:

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

Fields in a DPoP proof JWT header:

<table><thead><tr><th width="242.23046875">Field</th><th>Description</th></tr></thead><tbody><tr><td>typ</td><td>Type of token. Always set to <code>"dpop+jwt"</code>.</td></tr><tr><td>alg</td><td>Signing algorithm. The supported values are <code>ES256</code>, <code>ES256K</code>, <code>ES384</code>, <code>ES512</code>.</td></tr><tr><td>jwk</td><td>The public key corresponding to the private key used to sign the JWT. <strong>Must match</strong> the key used in the DPoP-bound token.</td></tr></tbody></table>

### DPoP JWT Payload Structure

Example of a DPoP proof JWT payload:

```json
{
  "htm": "POST",
  "htu": "https://id.corppass.gov.sg/mga/sps/oauth/oauth20/token",
  "jti": "5e6e5509-70e3-4bb7-ad30-4e176ffd3ca8",
  "iat": 1771819829,
  "exp": 1771819949,
  "ath": "hKzf0cQEXAMPLEUXxKXJvHr_F34Pp7pg6LFaP_LZ7Jw"
}
```

Fields in a DPoP proof JWT payload:

<table><thead><tr><th width="125.1875">Claim</th><th width="126.93359375">Required</th><th>Description</th></tr></thead><tbody><tr><td>htm</td><td>Yes</td><td>The HTTP method of the request (e.g., <code>POST</code>)</td></tr><tr><td>htu</td><td>Yes</td><td>The full HTTP URI of the request (e.g., <code>https://id.corppass.gov.sg/mga/sps/oauth/oauth20/token</code>).</td></tr><tr><td>jti</td><td>Yes</td><td>A unique identifier for the proof. Prevents replay attacks. Must be unique for each JWT.</td></tr><tr><td>iat</td><td>Yes</td><td>Issued-at time (epoch timestamp). Used to detect replayed proofs.</td></tr><tr><td>exp</td><td>No</td><td>The expiration time after which the JWT is no longer valid.</td></tr><tr><td>ath</td><td>No</td><td><p><strong>Required</strong> only when <strong>accessing protected resource endpoints</strong> (e.g. <code>/userinfo</code>). <br></p><p>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.<br><br>Read section <a href="#how-to-compute-the-access-token-hash-ath-claim">below</a> on how to compute this value.</p></td></tr></tbody></table>

#### How to compute the Access Token Hash (ath) claim

The `ath` (Access Token Hash) claim is a **mandatory** field in the DPoP proof JWT for requests to protected resource endpoints (eg. `/userinfo`).

It cryptographically binds the proof to the access token being used in a request, preventing replay attacks and misuse of tokens.

**Steps:**

1. **Start with the raw access token string** you received from the [Token endpoint](https://docs.corppass.gov.sg/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/3.-token-endpoint).
2. **Compute a SHA-256 hash** of the token.
3. **Base64URL-encode** the hash (no padding).

**Example (Pseudo-code)**

```javascript
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**

```json
"ath": "3rEbqHhURQcbfV3zM9sl1rsBzAcjT9TKqX3akHLZ9Nc"
```

**Example payload with `ath`**

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.corppass.gov.sg/technical-specifications/technical-concepts/demonstrating-proof-of-possession-dpop.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
