Skip to content

feat: [SIW-2206] Update PID data model and issuance flow to 1.0.0 #217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 40 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6dee770
chore: update credential issuer entity config types
gispada Mar 11, 2025
afd398e
chore: update PAR request
gispada Mar 11, 2025
d3f7673
chore: use OAuth-Client-Attestation headers in token endpoint
gispada Mar 11, 2025
ff21d2c
Revert "chore: use OAuth-Client-Attestation headers in token endpoint"
gispada Mar 11, 2025
f494b65
Revert "chore: update PAR request"
gispada Mar 11, 2025
647f832
chore: modify PAR request
gispada Mar 11, 2025
2313b80
chore: request object type
gispada Mar 12, 2025
743a478
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Mar 14, 2025
b8eadc9
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Mar 17, 2025
9cae318
chore: use the new structure of claims in credential_configurations_s…
gispada Mar 17, 2025
56dfc01
chore: update SdJwt4VC type
gispada Mar 18, 2025
9bce387
chore: fetch type metadata function
gispada Mar 18, 2025
a3832cc
chore: verify VCT integrity
gispada Mar 20, 2025
d2e728a
chore(sd-jwt): add issuing_authority and issuing_country
gispada Mar 20, 2025
59530e4
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Mar 25, 2025
56215b2
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Mar 31, 2025
b457507
Merge branch 'master' into SIW-2087-pid-0.9.x
RiccardoMolinari95 Mar 31, 2025
a93e0e0
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Apr 4, 2025
5b13b20
chore: remove comments
gispada Apr 4, 2025
23702ab
chore: switch vc+sd-jwt with dc+sd-jwt
gispada Apr 4, 2025
5d83716
Merge branch 'master' into SIW-2087-pid-0.9.x
RiccardoMolinari95 Apr 8, 2025
87790ba
rafactor: align to PID issuance 1.0
gispada Apr 11, 2025
83fdf4a
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Apr 14, 2025
acda57c
Merge branch 'SIW-2087-pid-0.9.x' into SIW-2206-pid-1.0.0
gispada Apr 14, 2025
8fad90e
chore: Wallet Attestation multiple formats
gispada Apr 16, 2025
d48df6f
chore: minor adjustments to types and requests
gispada Apr 16, 2025
da06e9d
chore: link TODO to Jira tasks
gispada Apr 16, 2025
140b10c
Merge branch 'master' into SIW-2206-pid-1.0.0
gispada Apr 16, 2025
cf41ff3
chore: use credential_identifier in obtainCredential
gispada Apr 18, 2025
1c1f547
Merge branch 'master' into SIW-2206-pid-1.0.0
gispada May 8, 2025
2a72fdf
Merge branch 'master' into SIW-2206-pid-1.0.0
gispada May 30, 2025
67cd7f5
Merge branch 'master' into SIW-2206-pid-1.0.0
gispada Jun 5, 2025
26dc364
chore: update wallet attestation endpoint
gispada Jun 5, 2025
dfa17bd
Merge branch 'master' into SIW-2206-pid-1.0.0
gispada Jun 6, 2025
24be6d1
chore: use oauth-client-attestation-pop+jwt for PoP token typ
gispada Jun 6, 2025
cca6112
chore: fix parsing errors
gispada Jun 6, 2025
65f4b02
chore: support multiple credentials in PAR request
gispada Jun 9, 2025
65d4aed
chore: edit obtain credential function
gispada Jun 9, 2025
b966891
chore: remove log
gispada Jun 9, 2025
f7ce8ac
chore: remove format from authorization_details
gispada Jun 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions example/src/thunks/pid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
// Start the issuance flow
const startFlow: Credential.Issuance.StartFlow = () => ({
issuerUrl: WALLET_PID_PROVIDER_BASE_URL,
credentialType: "PersonIdentificationData",
credentialType: "dc_sd_jwt_PersonIdentificationData",
});

const { issuerUrl, credentialType } = startFlow();
Expand All @@ -123,7 +123,7 @@
const { issuerRequestUri, clientId, codeVerifier, credentialDefinition } =
await Credential.Issuance.startUserAuthorization(
issuerConf,
credentialType,
[credentialType],
{
walletInstanceAttestation,
redirectUri: redirectUri,
Expand Down Expand Up @@ -161,7 +161,7 @@
export const continuePidFlowThunk = createAppAsyncThunk<
PidResult,
ContinuePidFlowThunkInput
>("pid/flowContinue", async (args, { getState }) => {

Check failure on line 164 in example/src/thunks/pid.ts

View workflow job for this annotation

GitHub Actions / code_review

Argument of type '(args: ContinuePidFlowThunkInput, { getState }: GetThunkAPI<{ state: { environment: EnvironmentState & PersistPartial; debug: DebugState; ... 5 more ...; presentation: PresentationState & PersistPartial; }; ... 6 more ...; rejectedMeta?: unknown; }>) => Promise<...>' is not assignable to parameter of type 'AsyncThunkPayloadCreator<PidResult, ContinuePidFlowThunkInput, { state: { environment: EnvironmentState & PersistPartial; ... 6 more ...; presentation: PresentationState & PersistPartial; }; ... 6 more ...; rejectedMeta?: unknown; }>'.
const { authRedirectUrl } = args;

const flowParams = selectPidFlowParams(getState());
Expand Down Expand Up @@ -213,25 +213,46 @@
}
);

const { credential, format } = await Credential.Issuance.obtainCredential(
const [pidCredentialDefinition] = credentialDefinition;

const { credential_configuration_id, credential_identifiers } =
accessToken.authorization_details.find(
(authDetails) =>
authDetails.credential_configuration_id ===
pidCredentialDefinition?.credential_configuration_id
) ?? {};

// Get the first credential_identifier from the access token's authorization details
const [credential_identifier] = credential_identifiers ?? [];

if (!credential_configuration_id) {
throw new Error("No credential configuration ID found for PID");
}

// Get all credentials that were authorized
const { credential } = await Credential.Issuance.obtainCredential(
issuerConf,
accessToken,
clientId,
credentialDefinition,
{
credential_configuration_id,
credential_identifier,
},
{
credentialCryptoContext,
dPopCryptoContext,
appFetch,
}
);

const { parsedCredential } =
await Credential.Issuance.verifyAndParseCredential(
issuerConf,
credential,
format,
{ credentialCryptoContext }
);
console.log(credential);

const parsedCredential = await Credential.Issuance.verifyAndParseCredential(
issuerConf,
credential,
format,

Check failure on line 253 in example/src/thunks/pid.ts

View workflow job for this annotation

GitHub Actions / code_review

Cannot find name 'format'. Did you mean 'FormData'?
{ credentialCryptoContext }
);

return {
parsedCredential,
Expand Down
81 changes: 51 additions & 30 deletions src/credential/issuance/03-start-user-authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import { generateRandomAlphaNumericString, type Out } from "../../utils/misc";
import type { EvaluateIssuerTrust } from "./02-evaluate-issuer-trust";
import type { StartFlow } from "./01-start-flow";
import { AuthorizationDetail, makeParRequest } from "../../utils/par";
import { ASSERTION_TYPE } from "./const";
import { LogLevel, Logger } from "../../utils/logging";

export type StartUserAuthorization = (
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
credentialType: Out<StartFlow>["credentialType"],
credentialType: string[],
context: {
wiaCryptoContext: CryptoContext;
walletInstanceAttestation: string;
Expand All @@ -20,18 +19,14 @@ export type StartUserAuthorization = (
issuerRequestUri: string;
clientId: string;
codeVerifier: string;
credentialDefinition: AuthorizationDetail;
credentialDefinition: AuthorizationDetail[];
}>;

/**
* Ensures that the credential type requested is supported by the issuer and contained in the
* issuer configuration.
* @param issuerConf The issuer configuration returned by {@link evaluateIssuerTrust}
* @param credentialType The type of the credential to be requested returned by {@link startFlow}
* @param context.wiaCryptoContext The Wallet Instance's crypto context
* @param context.walletInstanceAttestation The Wallet Instance's attestation
* @param context.redirectUri The redirect URI which is the custom URL scheme that the Wallet Instance is registered to handle
* @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
* @param credentialType The type of the credential to be requested;
* @returns The credential definition to be used in the request which includes the format and the type and its type
*/
const selectCredentialDefinition = (
Expand All @@ -43,9 +38,8 @@ const selectCredentialDefinition = (

const [result] = Object.keys(credential_configurations_supported)
.filter((e) => e.includes(credentialType))
.map((e) => ({
.map(() => ({
credential_configuration_id: credentialType,
format: credential_configurations_supported[e]!.format,
type: "openid_credential" as const,
}));

Expand All @@ -62,40 +56,61 @@ const selectCredentialDefinition = (
/**
* Ensures that the response mode requested is supported by the issuer and contained in the issuer configuration.
* @param issuerConf The issuer configuration
* @param credentialType The type of the credential to be requested
* @param credentialType The type of the credential(s) to be requested
* @returns The response mode to be used in the request, "query" for PersonIdentificationData and "form_post.jwt" for all other types.
*/
const selectResponseMode = (
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
credentialType: Out<StartFlow>["credentialType"]
credentialTypes: string[]
): ResponseMode => {
const responseModeSupported =
issuerConf.oauth_authorization_server.response_modes_supported;

const responseMode =
credentialType === "PersonIdentificationData" ? "query" : "form_post.jwt";
const responseModeSet = new Set<ResponseMode>();

for (const credentialType of credentialTypes) {
responseModeSet.add(
credentialType.match(/PersonIdentificationData/i)
? "query"
: "form_post.jwt"
);
}

if (responseModeSet.size !== 1) {
Logger.log(
LogLevel.ERROR,
`${credentialTypes} have incompatible response_mode: ${[...responseModeSet.values()]}`
);
throw new Error(
"Requested credentials have incompatible response_mode and cannot be requested with the same PAR request"
);
}

const [responseMode] = responseModeSet.values();

Logger.log(
LogLevel.DEBUG,
`Selected response mode ${responseMode} for credential type ${credentialType}`
`Selected response mode ${responseMode} for credential type ${credentialTypes}`
);

if (!responseModeSupported.includes(responseMode)) {
if (!responseModeSupported.includes(responseMode!)) {
Logger.log(
LogLevel.ERROR,
`Requested response mode ${responseMode} is not supported by the issuer according to its configuration ${JSON.stringify(responseModeSupported)}`
);
throw new Error(`No response mode support the type '${credentialType}'`);
throw new Error(`No response mode support the type '${credentialTypes}'`);
}

return responseMode;
return responseMode!;
};

/**
* WARNING: This function must be called after {@link evaluateIssuerTrust} and {@link startFlow}. The next steam is {@link compeUserAuthorizationWithQueryMode} or {@link compeUserAuthorizationWithFormPostJwtMode}
*
* Creates and sends a PAR request to the /as/par endpoint of the authorization server.
* This starts the authentication flow to obtain an access token.
* This token enables the Wallet Instance to request a digital credential from the Credential Endpoint of the Credential Issuer.
* This token enables the Wallet Instance to request a digital credential from the Credential Endpoint of the Credential Issuer; when multiple credential types are passed,
* it is possible to use the same access token for the issuance of all requested credentials.
* This is an HTTP POST request containing the Wallet Instance identifier (client id), the code challenge and challenge method as specified by PKCE according to RFC 9126
* along with the WTE and its proof of possession (WTE-PoP).
* Additionally, it includes a request object, which is a signed JWT encapsulating the type of digital credential requested (authorization_details),
Expand All @@ -105,15 +120,20 @@ const selectResponseMode = (
* to the Wallet Instance's Token Endpoint to obtain the Access Token, and the redirectUri of the Wallet Instance where the Authorization Response
* should be delivered. The redirect is achived by using a custom URL scheme that the Wallet Instance is registered to handle.
* @param issuerConf The issuer configuration
* @param credentialType The type of the credential to be requested returned by {@link selectCredentialDefinition}
* @param credentialType The type of the credential(s) to be requested
* @param ctx The context object containing the Wallet Instance's cryptographic context, the Wallet Instance's attestation, the redirect URI and the fetch implementation
* @returns The URI to which the end user should be redirected to start the authentication flow, along with the client id, the code verifier and the credential definition
*/

export const startUserAuthorization: StartUserAuthorization = async (
issuerConf,
credentialType,
ctx
) => {
const _credentialType = Array.isArray(credentialType)
? credentialType
: [credentialType];

const {
wiaCryptoContext,
walletInstanceAttestation,
Expand All @@ -122,6 +142,7 @@ export const startUserAuthorization: StartUserAuthorization = async (
} = ctx;

const clientId = await wiaCryptoContext.getPublicKey().then((_) => _.kid);

if (!clientId) {
Logger.log(
LogLevel.ERROR,
Expand All @@ -132,22 +153,22 @@ export const startUserAuthorization: StartUserAuthorization = async (
const codeVerifier = generateRandomAlphaNumericString(64);
const parEndpoint =
issuerConf.oauth_authorization_server.pushed_authorization_request_endpoint;
const credentialDefinition = selectCredentialDefinition(
issuerConf,
credentialType
const credentialDefinition = _credentialType.map((c) =>
selectCredentialDefinition(issuerConf, c)
);
const responseMode = selectResponseMode(issuerConf, credentialType);
const responseMode = selectResponseMode(issuerConf, _credentialType);

const getPar = makeParRequest({ wiaCryptoContext, appFetch });
const issuerRequestUri = await getPar(
clientId,
codeVerifier,
redirectUri,
responseMode,
parEndpoint,
walletInstanceAttestation,
[credentialDefinition],
ASSERTION_TYPE
{
clientId,
codeVerifier,
redirectUri,
responseMode,
authorizationDetails: credentialDefinition,
}
);

return { issuerRequestUri, clientId, codeVerifier, credentialDefinition };
Expand Down
8 changes: 3 additions & 5 deletions src/credential/issuance/05-authorize-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import { createPopToken } from "../../utils/pop";
import * as WalletInstanceAttestation from "../../wallet-instance-attestation";
import type { CryptoContext } from "@pagopa/io-react-native-jwt";
import { ASSERTION_TYPE } from "./const";
import { TokenResponse } from "./types";
import { IssuerResponseError, ValidationFailed } from "../../utils/errors";
import type { CompleteUserAuthorizationWithQueryMode } from "./04-complete-user-authorization";
Expand Down Expand Up @@ -47,7 +46,7 @@
export const authorizeAccess: AuthorizeAccess = async (
issuerConf,
code,
clientId,

Check failure on line 49 in src/credential/issuance/05-authorize-access.ts

View workflow job for this annotation

GitHub Actions / code_review

'clientId' is declared but its value is never read.
redirectUri,
codeVerifier,
context
Expand Down Expand Up @@ -92,12 +91,9 @@

const requestBody = {
grant_type: "authorization_code",
client_id: clientId,
code,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
client_assertion_type: ASSERTION_TYPE,
client_assertion: walletInstanceAttestation + "~" + signedWiaPoP,
redirect_uri: redirectUri,
};

const authorizationRequestFormBody = new URLSearchParams(requestBody);
Expand All @@ -112,6 +108,8 @@
headers: {
"Content-Type": "application/x-www-form-urlencoded",
DPoP: tokenRequestSignedDPop,
"OAuth-Client-Attestation": walletInstanceAttestation,
"OAuth-Client-Attestation-PoP": signedWiaPoP,
},
body: authorizationRequestFormBody.toString(),
})
Expand Down
Loading
Loading