Tabella dei contenuti
How to request a DPoP voucher for a producer's API (standard)
This tutorial explains how to request a voucher that uses Demonstrating Proof-of-Possession (DPoP) — the IETF standard (RFC 9449) that makes a voucher (JWT token) unusable if stolen, because it is bound to a public key owned by the caller. For more details, see the focus section.
More information about this implementation can be found in the dedicated section.
In essence, the end-to-end process requires seven steps:
- The consumer generates the standard client assertion; signs it with the private key whose public key is stored in their client on PDND.
- The consumer builds the DPoP intended for the PDND authorization server; signs it with a second private key whose public key will be inserted in the DPoP header, in the jwk field.
- The consumer requests the voucher from the PDND authorization server, adding the DPoP header.
- The PDND authorization server performs the necessary checks. If successful, it returns a DPoP-type voucher.
- The consumer builds a second DPoP, this time intended for the resource server, i.e., the producer’s e-service API; signs it with the same private key as the DPoP in step 2, again putting the corresponding public key in the DPoP header, in the jwk field.
- The consumer makes a request to the producer’s e-service; inserts both the voucher issued by PDND in the Authorization header, and the DPoP generated in the previous step in the DPoP header.
- The producer performs the necessary checks. If successful, it processes the consumer’s request.
It is assumed that the consumer has:
- Created an e-service type client (read tutorial).
- Generated at least one set of cryptographic material and uploaded the related public key to PDND within the client (read tutorial).
- Associated the client with the purpose for which they want to obtain or send data to the producer (read tutorial).
The first step is to build a valid client assertion. The client assertion is composed of a header and a payload containing the following fields.
Header:
Field name | Meaning |
---|---|
kid | the ID of the key used to sign the assertion, available on PDND |
alg | the algorithm used to sign the JWT (for now, always RS256) |
typ | the type of object being sent (always JWT) |
Payload:
Field name | Meaning |
---|---|
iss | the issuer, in this case the clientId |
sub | the subject, in this case always the clientId |
aud | the audience, available on PDND |
jti | the JWT ID, a unique random ID assigned by whoever is creating the token, used to track the token itself. It is the caller’s responsibility to ensure that the ID of this token is unique for the client assertion |
iat | issued at, the timestamp indicating when the token was created, expressed in UNIX epoch (numeric value, not a string) |
exp | expiration, the timestamp indicating when the token expires, expressed in UNIX epoch (numeric value, not a string) |
purposeId | The ID of the specific purpose for which you want to obtain a voucher, available in the back office |
As an example, here is a deserialized client assertion to highlight its contents.
Header:
1{
2 "alg": "RS256",
3 "kid": "2MJFa7aSSveFte8ULX9U-MaaygcoL5fBIJDTXBdba64",
4 "typ": "jwt"
5}
6
Payload:
1{
2 "iss": "8e9f24ca-78f5-4c69-9e4f-0efbeac7bb2b",
3 "sub": "8e9f24ca-78f5-4c69-9e4f-0efbeac7bb2b",
4 "aud": "auth.interop.pagopa.it/client-assertion",
5 "jti": "23387ac1-c192-4573-8350-207a4213d4be",
6 "iat": 1616170068,
7 "exp": 1616170668,
8 "purposeId": "34f1624b-91cb-4b05-b8c0-cad208a30222"
9}
10
After building a valid client assertion, it must be signed with your private key (the counterpart of the public key uploaded to the client in PDND).
For demonstration purposes, a Python script has been published showing how to perform the operation. All instructions are available in the back office, within your client.
A function is also available to check the validity of your client assertion and highlight any errors. The tool is available in the back office under Developers Tools > Debug client assertion.
The consumer then builds the DPoP intended for the PDND authorization server, which is a JWT with
Header:
1{
2 "typ": "dpop+jwt",
3 "alg": "ES256",
4 "jwk": "{CALLER_PUBLIC_KEY}"
5}
6
Payload:
1{
2 "htm": "POST",
3 "htu": "https://auth.interop.pagopa.it/token.oauth2",
4 "iat": 1747406361,
5 "jti": "b60203a7-6f31-4d08-a3d1-f69ba308eee0"
6}
7
where
Field name | Meaning |
---|---|
typ | must be set to dpop+jwt |
alg | Indicates the algorithm used to sign the DPoP. The recommended algorithm is ES256 |
jwk | The public key in JWK format corresponding to the private key used to sign the DPoP |
htm | Indicates the HTTP method being invoked. For obtaining a voucher from PDND, the method is POST |
htu | Indicates the URL being invoked. For obtaining a voucher from PDND in the Production environment it is https://auth.interop.pagopa.it/token.oauth2 (for Testing and Validation environments use the specific one provided in the back office) |
iat | Issued at — the timestamp (UNIX epoch, numeric) indicating the date and time when the DPoP is created |
jti | Unique identifier of the DPoP. It is the consumer’s responsibility to ensure that the ID of this token is unique and not reused |
The third step is to call the PDND authorization server with the signed client assertion to obtain in return a voucher that can be used with the PDND APIs.
In the request header, you must insert a DPoP header containing the DPoP generated in the previous step:
DPoP: <DPoP_proof>
The endpoint URL for the authorization server depends on the environment and will be clearly visible in the back office interface.
The endpoint must be called with the following body parameters:
Field name | Meaning |
---|---|
client_id | again, the clientId used in the assertion |
client_assertion | the signed client assertion from the first step |
client_assertion_type | the client assertion format, as indicated in RFC (always urn:ietf:params:oauth:client-assertion-type:jwt-bearer) |
grant_type | the type of flow used, as indicated in RFC (always client_credentials) |
The PDND authorization server performs the necessary checks, specifically:
- verifies the client-assertion according to the usual controls;
- verifies the DPoP signature using the public key in the jwk field of the header;
- checks that the htm and htu fields match the expected values for the current request;
- considers a proof valid only if presented within 60 seconds from its creation time (iat);
- verifies that the jti value has not already been used for another call to the PDND authorization server.
If valid, the PDND authorization server returns a DPoP-type voucher (token_type), signed as a JWT with a header "typ": "at+jwt" and containing a cnf.jkt claim.
Example of server response:
1{
2 "access_token": "eyJ0eXAiOiJhdCtqd3QiLC...",
3 "expires_in": 600,
4 "token_type": "DPoP"
5}
6
If we decode the access_token field, we get
Header:
1{
2 "typ": "dpop+jwt",
3 "alg": "RS256",
4 "use": "sig",
5 "kid": "{PDND_KEY_KID}"
6}
7
Payload:
1{
2 "iss": "interop.pagopa.it",
3 "nbf": 1747408537,
4 "iat": 1747408537,
5 "exp": 1747409537,
6 "jti": "12297ac1-c192-4573-8350-207a4213e5ac",
7 "aud": "https://eservice.pa.it/api/v1",
8 "sub": "9b361d49-33f4-4f1e-a88b-4e12661f2309",
9 "client_id": "9b361d49-33f4-4f1e-a88b-4e12661f2309",
10 "purposeId": "1b361d49-33f4-4f1e-a88b-4e12661f2300",
11 "producerId" : "0e9e2dab-2e93-4f24-ba59-38d9f11198ca",
12 "consumerId" : "69e2865e-65ab-4e48-a638-2037a9ee2ee7",
13 "eserviceId" : "b8c6d7ad-93fc-4eaf-9018-3cd8bf98163f",
14 "descriptorId": "9525a54b-9157-4b46-8976-ec66f20b7d7e",
15 "cnf": {
16 "jkt" : "L5TP6x6ved3p_jmIAtCiHMcNJeRrGWAusNnQkTTrnLY"
17 }
18}
19
The cnf.jkt field contains the thumbprint of the public key in JWK format (RFC 7638) used in the DPoP sent by the consumer (client) to the PDND authorization server.
Step 5 - The consumer builds a second DPoP
The consumer builds a second DPoP, this time intended for the producer’s e-service APIs. This second DPoP is similar to the one produced in step 2, with two differences:
- the htm and htu fields must refer to the resource being called on the producer’s server (as specified in the API interface file) instead of the PDND authorization server;
- another field, ath, must be added.
The ath field contains the hash of the voucher issued by PDND. This hash is obtained using SHA256 and must be Base64URL-encoded, as follows:
1BASE64URL(SHA-256(access_token_bytes))
2
Step 6 - Requesting data from the producer
The voucher must be inserted in the header of all subsequent calls to the producer’s APIs:
1Authorization: DPoP <voucher_issued_by_PDND>
2
The consumer must also insert another header:
DPoP: <DPoP_proof_generated_at_previous_step>
Step 7 - Waiting for the producer’s checks
The producer carries out all necessary checks. If everything is in order, it processes the consumer’s request, returning the requested data in the case of a data-providing e-service, or accepting the data from the consumer in the case of a data-receiving e-service.
To consult the recommended checks for producers, see the dedicated section.
In questa pagina
Summary of the flow
Prerequisites
Step 1 - Generating the client assertion
Step 2 - Generating the first DPoP
Step 3 - Requesting the voucher from the authorization server
Step 4 - The authorization server verifies and issues the voucher
Step 5 - The consumer builds a second DPoP
Step 6 - Requesting data from the producer
Step 7 - Waiting for the producer’s checks
Hai bisogno di aiuto?
Apri un ticket utilizzando l’apposita funzione all’interno della tua Area Riservata