DPoP and sender-constrained tokens
When a wallet requests a credential from an issuer, it first obtains an access token from the authorization server. Without additional protection, anyone who intercepts that token could use it to request credentials on the wallet's behalf. Demonstration of Proof-of-Possession (DPoP) solves this by binding each access token to a specific cryptographic key held by the wallet. The token becomes useless to any party that doesn't control the corresponding private key.
The Truvity EUDIW Connector requires DPoP for all credential issuance flows. The authorization server validates DPoP proofs during token exchange, and the connector validates DPoP-bound tokens during credential requests. Together, these checks ensure that only the wallet that originally requested the token can use it.
The problem with bearer tokens
Standard OAuth 2.0 access tokens are bearer tokens—any party that possesses the token can use it. This works well when tokens travel over secure channels and are short-lived, but it creates risk in credential issuance scenarios.
If an attacker intercepts a bearer token during an issuance flow, they could present it to the credential endpoint and receive a credential intended for someone else. The issuer has no way to distinguish the legitimate wallet from the attacker, because the token alone is sufficient for access. In a regulated ecosystem like EUDI, where credentials carry legal weight, this risk is unacceptable.
How DPoP works
DPoP replaces bearer tokens with sender-constrained tokens—tokens that are cryptographically bound to the wallet that requested them. The mechanism works in two stages.
First, when the wallet requests an access token, it generates a DPoP proof: a signed statement demonstrating that the wallet controls a specific key pair. The authorization server verifies this proof and issues an access token bound to that key. The token itself records which key it is bound to, so any party receiving the token can check whether the presenter actually controls the right key.
Second, when the wallet uses the access token to request a credential, it includes a fresh DPoP proof alongside the token. The connector verifies that the proof was signed by the same key the token is bound to. If the signatures don't match—because an attacker stole the token but doesn't have the wallet's private key—the request is rejected.
Security properties
DPoP provides three complementary security guarantees that together make token theft ineffective.
-
Token binding: The access token is cryptographically bound to the wallet's key. The authorization server records this binding when it issues the token, and the connector checks it when the token is presented. A stolen token cannot be used with a different key.
-
Replay prevention: Each DPoP proof includes a unique identifier and a timestamp. The authorization server and the connector reject proofs they have already seen or proofs that are too old. An attacker who captures a DPoP proof cannot replay it later.
-
Stolen token mitigation: Even if an attacker obtains both the access token and a DPoP proof, they cannot generate new proofs for future requests because they don't have the wallet's private key. The stolen token becomes useless after the captured proof expires or is consumed.
DPoP in the issuance flow
DPoP appears at two points in the credential issuance protocol.
During token exchange, the wallet presents a DPoP proof to the authorization server alongside the pre-authorized code from the credential offer. The authorization server validates the proof and issues a sender-constrained access token. This is the point where the token-to-key binding is established.
During the credential request, the wallet presents the sender-constrained access token to the connector along with a fresh DPoP proof. The connector validates that the proof was signed by the key bound to the token. Only after this validation does the connector proceed to sign and return the credential.
If a server determines that the DPoP proof requires a server-provided nonce, it returns a use_dpop_nonce error signaling the wallet to retry with the correct nonce. Per RFC 9449, this can happen at two points: the authorization server returns HTTP 400 at the token endpoint, and the resource server returns HTTP 401 at the credential endpoint. In both cases, the wallet reads the fresh nonce from the DPoP-Nonce response header and retries. This additional round trip prevents certain classes of replay attacks where an attacker might try to use a pre-generated proof.
Specification references
- RFC 9449 (DPoP)—Demonstration of Proof-of-Possession at the app layer
Further reading
- OID4VCI protocol—the credential issuance protocol that uses DPoP for token security
- Device binding—the related concept that binds credentials to the wallet's device during presentation
- Connector architecture—how the connector implements both issuance and verification protocols