@sphereon/oid4vci-client 0.15.2-next.3 → 0.15.2-next.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/AccessTokenClient.d.ts +7 -5
  2. package/dist/AccessTokenClient.d.ts.map +1 -1
  3. package/dist/AccessTokenClient.js +26 -5
  4. package/dist/AccessTokenClient.js.map +1 -1
  5. package/dist/AccessTokenClientV1_0_11.d.ts +7 -5
  6. package/dist/AccessTokenClientV1_0_11.d.ts.map +1 -1
  7. package/dist/AccessTokenClientV1_0_11.js +25 -5
  8. package/dist/AccessTokenClientV1_0_11.js.map +1 -1
  9. package/dist/AuthorizationCodeClient.d.ts.map +1 -1
  10. package/dist/AuthorizationCodeClient.js.map +1 -1
  11. package/dist/AuthorizationCodeClientV1_0_11.js +1 -1
  12. package/dist/AuthorizationCodeClientV1_0_11.js.map +1 -1
  13. package/dist/CredentialRequestClient.d.ts +5 -3
  14. package/dist/CredentialRequestClient.d.ts.map +1 -1
  15. package/dist/CredentialRequestClient.js +22 -4
  16. package/dist/CredentialRequestClient.js.map +1 -1
  17. package/dist/CredentialRequestClientV1_0_11.d.ts +5 -3
  18. package/dist/CredentialRequestClientV1_0_11.d.ts.map +1 -1
  19. package/dist/CredentialRequestClientV1_0_11.js +22 -4
  20. package/dist/CredentialRequestClientV1_0_11.js.map +1 -1
  21. package/dist/OpenID4VCIClient.d.ts +2 -1
  22. package/dist/OpenID4VCIClient.d.ts.map +1 -1
  23. package/dist/OpenID4VCIClient.js.map +1 -1
  24. package/dist/OpenID4VCIClientV1_0_11.d.ts +2 -1
  25. package/dist/OpenID4VCIClientV1_0_11.d.ts.map +1 -1
  26. package/dist/OpenID4VCIClientV1_0_11.js.map +1 -1
  27. package/dist/OpenID4VCIClientV1_0_13.d.ts +2 -1
  28. package/dist/OpenID4VCIClientV1_0_13.d.ts.map +1 -1
  29. package/dist/OpenID4VCIClientV1_0_13.js.map +1 -1
  30. package/dist/ProofOfPossessionBuilder.d.ts +2 -1
  31. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
  32. package/dist/ProofOfPossessionBuilder.js.map +1 -1
  33. package/dist/functions/AccessTokenUtil.d.ts.map +1 -1
  34. package/dist/functions/AccessTokenUtil.js +2 -2
  35. package/dist/functions/AccessTokenUtil.js.map +1 -1
  36. package/dist/functions/dpopUtil.d.ts +10 -0
  37. package/dist/functions/dpopUtil.d.ts.map +1 -0
  38. package/dist/functions/dpopUtil.js +30 -0
  39. package/dist/functions/dpopUtil.js.map +1 -0
  40. package/dist/functions/notifications.d.ts.map +1 -1
  41. package/dist/functions/notifications.js +3 -3
  42. package/dist/functions/notifications.js.map +1 -1
  43. package/lib/AccessTokenClient.ts +44 -7
  44. package/lib/AccessTokenClientV1_0_11.ts +43 -7
  45. package/lib/AuthorizationCodeClient.ts +1 -2
  46. package/lib/AuthorizationCodeClientV1_0_11.ts +1 -1
  47. package/lib/CredentialRequestClient.ts +38 -5
  48. package/lib/CredentialRequestClientV1_0_11.ts +38 -5
  49. package/lib/OpenID4VCIClient.ts +2 -2
  50. package/lib/OpenID4VCIClientV1_0_11.ts +1 -1
  51. package/lib/OpenID4VCIClientV1_0_13.ts +1 -1
  52. package/lib/ProofOfPossessionBuilder.ts +1 -1
  53. package/lib/__tests__/AccessTokenClient.spec.ts +4 -11
  54. package/lib/__tests__/SphereonE2E.spec.test.ts +3 -3
  55. package/lib/functions/AccessTokenUtil.ts +2 -2
  56. package/lib/functions/dpopUtil.ts +35 -0
  57. package/lib/functions/notifications.ts +2 -4
  58. package/package.json +4 -3
@@ -14,7 +14,7 @@ const oid4vci_common_1 = require("@sphereon/oid4vci-common");
14
14
  const types_1 = require("../types");
15
15
  function sendNotification(credentialRequestOpts, request, accessToken) {
16
16
  return __awaiter(this, void 0, void 0, function* () {
17
- var _a, _b, _c;
17
+ var _a;
18
18
  types_1.LOG.info(`Sending status notification event '${request.event}' for id ${request.notification_id}`);
19
19
  if (!credentialRequestOpts.notificationEndpoint) {
20
20
  throw Error(`Cannot send notification when no notification endpoint is provided`);
@@ -24,10 +24,10 @@ function sendNotification(credentialRequestOpts, request, accessToken) {
24
24
  const error = ((_a = response.errorBody) === null || _a === void 0 ? void 0 : _a.error) !== undefined;
25
25
  const result = {
26
26
  error,
27
- response: error ? yield ((_b = response.errorBody) === null || _b === void 0 ? void 0 : _b.json()) : undefined,
27
+ response: error ? response.errorBody : undefined,
28
28
  };
29
29
  if (error) {
30
- types_1.LOG.warning(`Notification endpoint returned an error for event '${request.event}' and id ${request.notification_id}: ${yield ((_c = response.errorBody) === null || _c === void 0 ? void 0 : _c.json())}`);
30
+ types_1.LOG.warning(`Notification endpoint returned an error for event '${request.event}' and id ${request.notification_id}: ${response.errorBody}`);
31
31
  }
32
32
  else {
33
33
  types_1.LOG.debug(`Notification endpoint returned success for event '${request.event}' and id ${request.notification_id}`);
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../lib/functions/notifications.ts"],"names":[],"mappings":";;;;;;;;;;;AAKA,4CA0BC;AA/BD,6DAAoH;AAGpH,oCAA+B;AAE/B,SAAsB,gBAAgB,CACpC,qBAAqD,EACrD,OAA4B,EAC5B,WAAoB;;;QAEpB,WAAG,CAAC,IAAI,CAAC,sCAAsC,OAAO,CAAC,KAAK,YAAY,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QACnG,IAAI,CAAC,qBAAqB,CAAC,oBAAoB,EAAE,CAAC;YAChD,MAAM,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACpF,CAAC;QACD,MAAM,KAAK,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,qBAAqB,CAAC,KAAK,CAAC;QACzD,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAI,EAA4B,qBAAqB,CAAC,oBAAoB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,oBACrH,CAAC,KAAK,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EACpC,CAAC;QACH,MAAM,KAAK,GAAG,CAAA,MAAA,QAAQ,CAAC,SAAS,0CAAE,KAAK,MAAK,SAAS,CAAC;QACtD,MAAM,MAAM,GAAG;YACb,KAAK;YACL,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAA,MAAA,QAAQ,CAAC,SAAS,0CAAE,IAAI,EAAE,CAAA,CAAC,CAAC,CAAC,SAAS;SAC/D,CAAC;QACF,IAAI,KAAK,EAAE,CAAC;YACV,WAAG,CAAC,OAAO,CACT,sDAAsD,OAAO,CAAC,KAAK,YAAY,OAAO,CAAC,eAAe,KAAK,MAAM,CAAA,MAAA,QAAQ,CAAC,SAAS,0CAAE,IAAI,EAAE,CAAA,EAAE,CAC9I,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,WAAG,CAAC,KAAK,CAAC,qDAAqD,OAAO,CAAC,KAAK,YAAY,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QACrH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CAAA"}
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../lib/functions/notifications.ts"],"names":[],"mappings":";;;;;;;;;;;AAKA,4CAwBC;AA7BD,6DAAoH;AAGpH,oCAA+B;AAE/B,SAAsB,gBAAgB,CACpC,qBAAqD,EACrD,OAA4B,EAC5B,WAAoB;;;QAEpB,WAAG,CAAC,IAAI,CAAC,sCAAsC,OAAO,CAAC,KAAK,YAAY,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QACnG,IAAI,CAAC,qBAAqB,CAAC,oBAAoB,EAAE,CAAC;YAChD,MAAM,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACpF,CAAC;QACD,MAAM,KAAK,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,qBAAqB,CAAC,KAAK,CAAC;QACzD,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAI,EAA4B,qBAAqB,CAAC,oBAAoB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,oBACrH,CAAC,KAAK,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EACpC,CAAC;QACH,MAAM,KAAK,GAAG,CAAA,MAAA,QAAQ,CAAC,SAAS,0CAAE,KAAK,MAAK,SAAS,CAAC;QACtD,MAAM,MAAM,GAAG;YACb,KAAK;YACL,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;SACjD,CAAC;QACF,IAAI,KAAK,EAAE,CAAC;YACV,WAAG,CAAC,OAAO,CAAC,sDAAsD,OAAO,CAAC,KAAK,YAAY,OAAO,CAAC,eAAe,KAAK,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;QAC/I,CAAC;aAAM,CAAC;YACN,WAAG,CAAC,KAAK,CAAC,qDAAqD,OAAO,CAAC,KAAK,YAAY,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QACrH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CAAA"}
@@ -1,3 +1,4 @@
1
+ import { createDPoP, CreateDPoPClientOpts, getCreateDPoPOptions } from '@sphereon/oid4vc-common';
1
2
  import {
2
3
  AccessTokenRequest,
3
4
  AccessTokenRequestOpts,
@@ -6,6 +7,7 @@ import {
6
7
  AuthorizationServerOpts,
7
8
  AuthzFlowType,
8
9
  convertJsonToURI,
10
+ DPoPResponseParams,
9
11
  EndpointMetadata,
10
12
  formPost,
11
13
  getIssuerFromCredentialOfferPayload,
@@ -24,11 +26,12 @@ import { ObjectUtils } from '@sphereon/ssi-types';
24
26
 
25
27
  import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
26
28
  import { createJwtBearerClientAssertion } from './functions';
29
+ import { shouldRetryTokenRequestWithDPoPNonce } from './functions/dpopUtil';
27
30
  import { LOG } from './types';
28
31
 
29
32
  export class AccessTokenClient {
30
- public async acquireAccessToken(opts: AccessTokenRequestOpts): Promise<OpenIDResponse<AccessTokenResponse>> {
31
- const { asOpts, pin, codeVerifier, code, redirectUri, metadata } = opts;
33
+ public async acquireAccessToken(opts: AccessTokenRequestOpts): Promise<OpenIDResponse<AccessTokenResponse, DPoPResponseParams>> {
34
+ const { asOpts, pin, codeVerifier, code, redirectUri, metadata, createDPoPOpts } = opts;
32
35
 
33
36
  const credentialOffer = opts.credentialOffer ? await assertedUniformCredentialOffer(opts.credentialOffer) : undefined;
34
37
  const pinMetadata: TxCodeAndPinRequired | undefined = credentialOffer && this.getPinMetadata(credentialOffer.credential_offer);
@@ -59,6 +62,7 @@ export class AccessTokenClient {
59
62
  metadata,
60
63
  asOpts,
61
64
  issuerOpts,
65
+ createDPoPOpts: createDPoPOpts,
62
66
  });
63
67
  }
64
68
 
@@ -68,13 +72,15 @@ export class AccessTokenClient {
68
72
  metadata,
69
73
  asOpts,
70
74
  issuerOpts,
75
+ createDPoPOpts,
71
76
  }: {
72
77
  accessTokenRequest: AccessTokenRequest;
73
78
  pinMetadata?: TxCodeAndPinRequired;
74
79
  metadata?: EndpointMetadata;
75
80
  asOpts?: AuthorizationServerOpts;
76
81
  issuerOpts?: IssuerOpts;
77
- }): Promise<OpenIDResponse<AccessTokenResponse>> {
82
+ createDPoPOpts?: CreateDPoPClientOpts;
83
+ }): Promise<OpenIDResponse<AccessTokenResponse, DPoPResponseParams>> {
78
84
  this.validate(accessTokenRequest, pinMetadata);
79
85
 
80
86
  const requestTokenURL = AccessTokenClient.determineTokenURL({
@@ -87,10 +93,34 @@ export class AccessTokenClient {
87
93
  : undefined,
88
94
  });
89
95
 
90
- return this.sendAuthCode(requestTokenURL, accessTokenRequest);
96
+ const useDpop = createDPoPOpts?.dPoPSigningAlgValuesSupported && createDPoPOpts.dPoPSigningAlgValuesSupported.length > 0;
97
+ let dPoP = useDpop ? await createDPoP(getCreateDPoPOptions(createDPoPOpts, requestTokenURL)) : undefined;
98
+
99
+ let response = await this.sendAuthCode(requestTokenURL, accessTokenRequest, dPoP ? { headers: { dpop: dPoP } } : undefined);
100
+
101
+ let nextDPoPNonce = createDPoPOpts?.jwtPayloadProps.nonce;
102
+ const retryWithNonce = shouldRetryTokenRequestWithDPoPNonce(response);
103
+ if (retryWithNonce.ok && createDPoPOpts) {
104
+ createDPoPOpts.jwtPayloadProps.nonce = retryWithNonce.dpopNonce;
105
+
106
+ dPoP = await createDPoP(getCreateDPoPOptions(createDPoPOpts, requestTokenURL));
107
+ response = await this.sendAuthCode(requestTokenURL, accessTokenRequest, dPoP ? { headers: { dpop: dPoP } } : undefined);
108
+ const successDPoPNonce = response.origResponse.headers.get('DPoP-Nonce');
109
+
110
+ nextDPoPNonce = successDPoPNonce ?? retryWithNonce.dpopNonce;
111
+ }
112
+
113
+ if (response.successBody && createDPoPOpts && response.successBody.token_type !== 'DPoP') {
114
+ throw new Error('Invalid token type returned. Expected DPoP. Received: ' + response.successBody.token_type);
115
+ }
116
+
117
+ return {
118
+ ...response,
119
+ params: { ...(nextDPoPNonce && { dpop: { dpopNonce: nextDPoPNonce } }) },
120
+ };
91
121
  }
92
122
 
93
- public async createAccessTokenRequest(opts: AccessTokenRequestOpts): Promise<AccessTokenRequest> {
123
+ public async createAccessTokenRequest(opts: Omit<AccessTokenRequestOpts, 'createDPoPOpts'>): Promise<AccessTokenRequest> {
94
124
  const { asOpts, pin, codeVerifier, code, redirectUri } = opts;
95
125
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
96
126
  // @ts-ignore
@@ -105,6 +135,7 @@ export class AccessTokenClient {
105
135
  if (credentialOfferRequest?.supportedFlows.includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
106
136
  this.assertAlphanumericPin(opts.pinMetadata, pin);
107
137
  request.user_pin = pin;
138
+ request.tx_code = pin;
108
139
 
109
140
  request.grant_type = GrantTypes.PRE_AUTHORIZED_CODE;
110
141
  // we actually know it is there because of the isPreAuthCode call
@@ -221,8 +252,14 @@ export class AccessTokenClient {
221
252
  }
222
253
  }
223
254
 
224
- private async sendAuthCode(requestTokenURL: string, accessTokenRequest: AccessTokenRequest): Promise<OpenIDResponse<AccessTokenResponse>> {
225
- return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }));
255
+ private async sendAuthCode(
256
+ requestTokenURL: string,
257
+ accessTokenRequest: AccessTokenRequest,
258
+ opts?: { headers?: Record<string, string> },
259
+ ): Promise<OpenIDResponse<AccessTokenResponse, DPoPResponseParams>> {
260
+ return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }), {
261
+ customHeaders: opts?.headers ? opts.headers : undefined,
262
+ });
226
263
  }
227
264
 
228
265
  public static determineTokenURL({
@@ -1,3 +1,4 @@
1
+ import { createDPoP, CreateDPoPClientOpts, getCreateDPoPOptions } from '@sphereon/oid4vc-common';
1
2
  import {
2
3
  AccessTokenRequest,
3
4
  AccessTokenRequestOpts,
@@ -8,6 +9,7 @@ import {
8
9
  convertJsonToURI,
9
10
  CredentialOfferV1_0_11,
10
11
  CredentialOfferV1_0_13,
12
+ DPoPResponseParams,
11
13
  EndpointMetadata,
12
14
  formPost,
13
15
  getIssuerFromCredentialOfferPayload,
@@ -27,12 +29,13 @@ import Debug from 'debug';
27
29
 
28
30
  import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
29
31
  import { createJwtBearerClientAssertion } from './functions';
32
+ import { shouldRetryTokenRequestWithDPoPNonce } from './functions/dpopUtil';
30
33
 
31
34
  const debug = Debug('sphereon:oid4vci:token');
32
35
 
33
36
  export class AccessTokenClientV1_0_11 {
34
- public async acquireAccessToken(opts: AccessTokenRequestOpts): Promise<OpenIDResponse<AccessTokenResponse>> {
35
- const { asOpts, pin, codeVerifier, code, redirectUri, metadata } = opts;
37
+ public async acquireAccessToken(opts: AccessTokenRequestOpts): Promise<OpenIDResponse<AccessTokenResponse, DPoPResponseParams>> {
38
+ const { asOpts, pin, codeVerifier, code, redirectUri, metadata, createDPoPOpts } = opts;
36
39
 
37
40
  const credentialOffer = opts.credentialOffer ? await assertedUniformCredentialOffer(opts.credentialOffer) : undefined;
38
41
  const isPinRequired = credentialOffer && this.isPinRequiredValue(credentialOffer.credential_offer);
@@ -63,6 +66,7 @@ export class AccessTokenClientV1_0_11 {
63
66
  metadata,
64
67
  asOpts,
65
68
  issuerOpts,
69
+ createDPoPOpts,
66
70
  });
67
71
  }
68
72
 
@@ -71,6 +75,7 @@ export class AccessTokenClientV1_0_11 {
71
75
  isPinRequired,
72
76
  metadata,
73
77
  asOpts,
78
+ createDPoPOpts,
74
79
  issuerOpts,
75
80
  }: {
76
81
  accessTokenRequest: AccessTokenRequest;
@@ -78,7 +83,8 @@ export class AccessTokenClientV1_0_11 {
78
83
  metadata?: EndpointMetadata;
79
84
  asOpts?: AuthorizationServerOpts;
80
85
  issuerOpts?: IssuerOpts;
81
- }): Promise<OpenIDResponse<AccessTokenResponse>> {
86
+ createDPoPOpts?: CreateDPoPClientOpts;
87
+ }): Promise<OpenIDResponse<AccessTokenResponse, DPoPResponseParams>> {
82
88
  this.validate(accessTokenRequest, isPinRequired);
83
89
 
84
90
  const requestTokenURL = AccessTokenClientV1_0_11.determineTokenURL({
@@ -91,10 +97,34 @@ export class AccessTokenClientV1_0_11 {
91
97
  : undefined,
92
98
  });
93
99
 
94
- return this.sendAuthCode(requestTokenURL, accessTokenRequest);
100
+ const useDpop = createDPoPOpts?.dPoPSigningAlgValuesSupported && createDPoPOpts.dPoPSigningAlgValuesSupported.length > 0;
101
+ let dPoP = useDpop ? await createDPoP(getCreateDPoPOptions(createDPoPOpts, requestTokenURL)) : undefined;
102
+
103
+ let response = await this.sendAuthCode(requestTokenURL, accessTokenRequest, dPoP ? { headers: { dpop: dPoP } } : undefined);
104
+
105
+ let nextDPoPNonce = createDPoPOpts?.jwtPayloadProps.nonce;
106
+ const retryWithNonce = shouldRetryTokenRequestWithDPoPNonce(response);
107
+ if (retryWithNonce.ok && createDPoPOpts) {
108
+ createDPoPOpts.jwtPayloadProps.nonce = retryWithNonce.dpopNonce;
109
+
110
+ dPoP = await createDPoP(getCreateDPoPOptions(createDPoPOpts, requestTokenURL));
111
+ response = await this.sendAuthCode(requestTokenURL, accessTokenRequest, dPoP ? { headers: { dpop: dPoP } } : undefined);
112
+ const successDPoPNonce = response.origResponse.headers.get('DPoP-Nonce');
113
+
114
+ nextDPoPNonce = successDPoPNonce ?? retryWithNonce.dpopNonce;
115
+ }
116
+
117
+ if (response.successBody && createDPoPOpts && response.successBody.token_type !== 'DPoP') {
118
+ throw new Error('Invalid token type returned. Expected DPoP. Received: ' + response.successBody.token_type);
119
+ }
120
+
121
+ return {
122
+ ...response,
123
+ params: { ...(nextDPoPNonce && { dpop: { dpopNonce: nextDPoPNonce } }) },
124
+ };
95
125
  }
96
126
 
97
- public async createAccessTokenRequest(opts: AccessTokenRequestOpts): Promise<AccessTokenRequest> {
127
+ public async createAccessTokenRequest(opts: Omit<AccessTokenRequestOpts, 'createDPoPOpts'>): Promise<AccessTokenRequest> {
98
128
  const { asOpts, pin, codeVerifier, code, redirectUri } = opts;
99
129
  const credentialOfferRequest = opts.credentialOffer
100
130
  ? await toUniformCredentialOfferRequest(opts.credentialOffer as CredentialOfferV1_0_11 | CredentialOfferV1_0_13)
@@ -204,8 +234,14 @@ export class AccessTokenClientV1_0_11 {
204
234
  }
205
235
  }
206
236
 
207
- private async sendAuthCode(requestTokenURL: string, accessTokenRequest: AccessTokenRequest): Promise<OpenIDResponse<AccessTokenResponse>> {
208
- return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }));
237
+ private async sendAuthCode(
238
+ requestTokenURL: string,
239
+ accessTokenRequest: AccessTokenRequest,
240
+ opts?: { headers?: Record<string, string> },
241
+ ): Promise<OpenIDResponse<AccessTokenResponse>> {
242
+ return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }), {
243
+ customHeaders: opts?.headers ? opts.headers : undefined,
244
+ });
209
245
  }
210
246
 
211
247
  public static determineTokenURL({
@@ -114,7 +114,7 @@ export const createAuthorizationRequestUrl = async ({
114
114
  const client_id = clientId ?? authorizationRequest.clientId;
115
115
 
116
116
  // Authorization server metadata takes precedence
117
- const authorizationMetadata = endpointMetadata.authorizationServerMetadata ?? endpointMetadata.credentialIssuerMetadata
117
+ const authorizationMetadata = endpointMetadata.authorizationServerMetadata ?? endpointMetadata.credentialIssuerMetadata;
118
118
 
119
119
  let { authorizationDetails } = authorizationRequest;
120
120
  const parMode = authorizationMetadata?.require_pushed_authorization_requests
@@ -182,7 +182,6 @@ export const createAuthorizationRequestUrl = async ({
182
182
  }
183
183
  const parEndpoint = authorizationMetadata?.pushed_authorization_request_endpoint;
184
184
 
185
-
186
185
  let queryObj: Record<string, any> | PushedAuthorizationResponse = {
187
186
  response_type: ResponseType.AUTH_CODE,
188
187
  ...(!pkce.disabled && {
@@ -40,7 +40,7 @@ export const createAuthorizationRequestUrlV1_0_11 = async ({
40
40
 
41
41
  const parMode = endpointMetadata?.credentialIssuerMetadata?.require_pushed_authorization_requests
42
42
  ? PARMode.REQUIRE
43
- : authorizationRequest.parMode ?? PARMode.AUTO;
43
+ : (authorizationRequest.parMode ?? PARMode.AUTO);
44
44
  // Scope and authorization_details can be used in the same authorization request
45
45
  // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
46
46
  if (!scope && !authorizationDetails) {
@@ -1,7 +1,9 @@
1
+ import { createDPoP, CreateDPoPClientOpts, getCreateDPoPOptions } from '@sphereon/oid4vc-common';
1
2
  import {
2
3
  acquireDeferredCredential,
3
4
  CredentialRequestV1_0_13,
4
5
  CredentialResponse,
6
+ DPoPResponseParams,
5
7
  getCredentialRequestForVersion,
6
8
  getUniformFormat,
7
9
  isDeferredCredentialResponse,
@@ -21,6 +23,7 @@ import Debug from 'debug';
21
23
  import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
22
24
  import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';
23
25
  import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
26
+ import { shouldRetryResourceRequestWithDPoPNonce } from './functions/dpopUtil';
24
27
 
25
28
  const debug = Debug('sphereon:oid4vci:credential');
26
29
 
@@ -89,7 +92,8 @@ export class CredentialRequestClient {
89
92
  context?: string[];
90
93
  format?: CredentialFormat | OID4VCICredentialFormat;
91
94
  subjectIssuance?: ExperimentalSubjectIssuance;
92
- }): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
95
+ createDPoPOpts?: CreateDPoPClientOpts;
96
+ }): Promise<OpenIDResponse<CredentialResponse, DPoPResponseParams> & { access_token: string }> {
93
97
  const { credentialIdentifier, credentialTypes, proofInput, format, context, subjectIssuance } = opts;
94
98
 
95
99
  const request = await this.createCredentialRequest({
@@ -101,12 +105,13 @@ export class CredentialRequestClient {
101
105
  credentialIdentifier,
102
106
  subjectIssuance,
103
107
  });
104
- return await this.acquireCredentialsUsingRequest(request);
108
+ return await this.acquireCredentialsUsingRequest(request, opts.createDPoPOpts);
105
109
  }
106
110
 
107
111
  public async acquireCredentialsUsingRequest(
108
112
  uniformRequest: UniformCredentialRequest,
109
- ): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
113
+ createDPoPOpts?: CreateDPoPClientOpts,
114
+ ): Promise<OpenIDResponse<CredentialResponse, DPoPResponseParams> & { access_token: string }> {
110
115
  if (this.version() < OpenId4VCIVersion.VER_1_0_13) {
111
116
  throw new Error('Versions below v1.0.13 (draft 13) are not supported by the V13 credential request client.');
112
117
  }
@@ -119,9 +124,33 @@ export class CredentialRequestClient {
119
124
  debug(`Acquiring credential(s) from: ${credentialEndpoint}`);
120
125
  debug(`request\n: ${JSON.stringify(request, null, 2)}`);
121
126
  const requestToken: string = this.credentialRequestOpts.token;
122
- let response = (await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken })) as OpenIDResponse<CredentialResponse> & {
127
+
128
+ let dPoP = createDPoPOpts ? await createDPoP(getCreateDPoPOptions(createDPoPOpts, credentialEndpoint, { accessToken: requestToken })) : undefined;
129
+
130
+ let response = (await post(credentialEndpoint, JSON.stringify(request), {
131
+ bearerToken: requestToken,
132
+ customHeaders: { ...(dPoP && { dpop: dPoP }) },
133
+ })) as OpenIDResponse<CredentialResponse> & {
123
134
  access_token: string;
124
135
  };
136
+
137
+ let nextDPoPNonce = createDPoPOpts?.jwtPayloadProps.nonce;
138
+ const retryWithNonce = shouldRetryResourceRequestWithDPoPNonce(response);
139
+ if (retryWithNonce.ok && createDPoPOpts) {
140
+ createDPoPOpts.jwtPayloadProps.nonce = retryWithNonce.dpopNonce;
141
+ dPoP = await createDPoP(getCreateDPoPOptions(createDPoPOpts, credentialEndpoint, { accessToken: requestToken }));
142
+
143
+ response = (await post(credentialEndpoint, JSON.stringify(request), {
144
+ bearerToken: requestToken,
145
+ customHeaders: { ...(createDPoPOpts && { dpop: dPoP }) },
146
+ })) as OpenIDResponse<CredentialResponse> & {
147
+ access_token: string;
148
+ };
149
+
150
+ const successDPoPNonce = response.origResponse.headers.get('DPoP-Nonce');
151
+ nextDPoPNonce = successDPoPNonce ?? retryWithNonce.dpopNonce;
152
+ }
153
+
125
154
  this._isDeferred = isDeferredCredentialResponse(response);
126
155
  if (this.isDeferred() && this.credentialRequestOpts.deferredCredentialAwait && response.successBody) {
127
156
  response = await this.acquireDeferredCredential(response.successBody, { bearerToken: this.credentialRequestOpts.token });
@@ -134,7 +163,11 @@ export class CredentialRequestClient {
134
163
  }
135
164
  }
136
165
  debug(`Credential endpoint ${credentialEndpoint} response:\r\n${JSON.stringify(response, null, 2)}`);
137
- return response;
166
+
167
+ return {
168
+ ...response,
169
+ params: { ...(nextDPoPNonce && { dpop: { dpopNonce: nextDPoPNonce } }) },
170
+ };
138
171
  }
139
172
 
140
173
  public async acquireDeferredCredential(
@@ -1,6 +1,8 @@
1
+ import { createDPoP, CreateDPoPClientOpts, getCreateDPoPOptions } from '@sphereon/oid4vc-common';
1
2
  import {
2
3
  acquireDeferredCredential,
3
4
  CredentialResponse,
5
+ DPoPResponseParams,
4
6
  getCredentialRequestForVersion,
5
7
  getUniformFormat,
6
8
  isDeferredCredentialResponse,
@@ -20,6 +22,7 @@ import Debug from 'debug';
20
22
  import { buildProof } from './CredentialRequestClient';
21
23
  import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
22
24
  import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
25
+ import { shouldRetryResourceRequestWithDPoPNonce } from './functions/dpopUtil';
23
26
 
24
27
  const debug = Debug('sphereon:oid4vci:credential');
25
28
 
@@ -64,16 +67,18 @@ export class CredentialRequestClientV1_0_11 {
64
67
  credentialTypes?: string | string[];
65
68
  context?: string[];
66
69
  format?: CredentialFormat | OID4VCICredentialFormat;
67
- }): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
70
+ createDPoPOpts?: CreateDPoPClientOpts;
71
+ }): Promise<OpenIDResponse<CredentialResponse, DPoPResponseParams> & { access_token: string }> {
68
72
  const { credentialTypes, proofInput, format, context } = opts;
69
73
 
70
74
  const request = await this.createCredentialRequest({ proofInput, credentialTypes, context, format, version: this.version() });
71
- return await this.acquireCredentialsUsingRequest(request);
75
+ return await this.acquireCredentialsUsingRequest(request, opts.createDPoPOpts);
72
76
  }
73
77
 
74
78
  public async acquireCredentialsUsingRequest(
75
79
  uniformRequest: UniformCredentialRequest,
76
- ): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
80
+ createDPoPOpts?: CreateDPoPClientOpts,
81
+ ): Promise<OpenIDResponse<CredentialResponse, DPoPResponseParams> & { access_token: string }> {
77
82
  const request = getCredentialRequestForVersion(uniformRequest, this.version());
78
83
  const credentialEndpoint: string = this.credentialRequestOpts.credentialEndpoint;
79
84
  if (!isValidURL(credentialEndpoint)) {
@@ -83,9 +88,33 @@ export class CredentialRequestClientV1_0_11 {
83
88
  debug(`Acquiring credential(s) from: ${credentialEndpoint}`);
84
89
  debug(`request\n: ${JSON.stringify(request, null, 2)}`);
85
90
  const requestToken: string = this.credentialRequestOpts.token;
86
- let response = (await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken })) as OpenIDResponse<CredentialResponse> & {
91
+
92
+ let dPoP = createDPoPOpts ? await createDPoP(getCreateDPoPOptions(createDPoPOpts, credentialEndpoint, { accessToken: requestToken })) : undefined;
93
+
94
+ let response = (await post(credentialEndpoint, JSON.stringify(request), {
95
+ bearerToken: requestToken,
96
+ customHeaders: { ...(createDPoPOpts && { dpop: dPoP }) },
97
+ })) as OpenIDResponse<CredentialResponse> & {
87
98
  access_token: string;
88
99
  };
100
+
101
+ let nextDPoPNonce = createDPoPOpts?.jwtPayloadProps.nonce;
102
+ const retryWithNonce = shouldRetryResourceRequestWithDPoPNonce(response);
103
+ if (retryWithNonce.ok && createDPoPOpts) {
104
+ createDPoPOpts.jwtPayloadProps.nonce = retryWithNonce.dpopNonce;
105
+ dPoP = await createDPoP(getCreateDPoPOptions(createDPoPOpts, credentialEndpoint, { accessToken: requestToken }));
106
+
107
+ response = (await post(credentialEndpoint, JSON.stringify(request), {
108
+ bearerToken: requestToken,
109
+ customHeaders: { ...(createDPoPOpts && { dpop: dPoP }) },
110
+ })) as OpenIDResponse<CredentialResponse> & {
111
+ access_token: string;
112
+ };
113
+
114
+ const successDPoPNonce = response.origResponse.headers.get('DPoP-Nonce');
115
+ nextDPoPNonce = successDPoPNonce ?? retryWithNonce.dpopNonce;
116
+ }
117
+
89
118
  this._isDeferred = isDeferredCredentialResponse(response);
90
119
  if (this.isDeferred() && this.credentialRequestOpts.deferredCredentialAwait && response.successBody) {
91
120
  response = await this.acquireDeferredCredential(response.successBody, { bearerToken: this.credentialRequestOpts.token });
@@ -93,7 +122,11 @@ export class CredentialRequestClientV1_0_11 {
93
122
  response.access_token = requestToken;
94
123
 
95
124
  debug(`Credential endpoint ${credentialEndpoint} response:\r\n${JSON.stringify(response, null, 2)}`);
96
- return response;
125
+
126
+ return {
127
+ ...response,
128
+ params: { ...(nextDPoPNonce && { dpop: { dpopNonce: nextDPoPNonce } }) },
129
+ };
97
130
  }
98
131
 
99
132
  public async acquireDeferredCredential(
@@ -1,3 +1,4 @@
1
+ import { JWK } from '@sphereon/oid4vc-common';
1
2
  import {
2
3
  AccessTokenResponse,
3
4
  Alg,
@@ -23,7 +24,6 @@ import {
23
24
  getSupportedCredentials,
24
25
  getTypesFromCredentialSupported,
25
26
  getTypesFromObject,
26
- JWK,
27
27
  KID_JWK_X5C_ERROR,
28
28
  NotificationRequest,
29
29
  NotificationResult,
@@ -549,7 +549,7 @@ export class OpenID4VCIClient {
549
549
  issuerSupportedFlowTypes(): AuthzFlowType[] {
550
550
  return (
551
551
  this.credentialOffer?.supportedFlows ??
552
- (this._state.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint ?? this._state.endpointMetadata?.authorization_server
552
+ ((this._state.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint ?? this._state.endpointMetadata?.authorization_server)
553
553
  ? [AuthzFlowType.AUTHORIZATION_CODE_FLOW]
554
554
  : [])
555
555
  );
@@ -1,3 +1,4 @@
1
+ import { JWK } from '@sphereon/oid4vc-common';
1
2
  import {
2
3
  AccessTokenResponse,
3
4
  Alg,
@@ -19,7 +20,6 @@ import {
19
20
  getSupportedCredentials,
20
21
  getTypesFromCredentialSupported,
21
22
  getTypesFromObject,
22
- JWK,
23
23
  KID_JWK_X5C_ERROR,
24
24
  OID4VCICredentialFormat,
25
25
  OpenId4VCIVersion,
@@ -1,3 +1,4 @@
1
+ import { JWK } from '@sphereon/oid4vc-common';
1
2
  import {
2
3
  AccessTokenResponse,
3
4
  Alg,
@@ -17,7 +18,6 @@ import {
17
18
  getIssuerFromCredentialOfferPayload,
18
19
  getSupportedCredentials,
19
20
  getTypesFromCredentialSupported,
20
- JWK,
21
21
  KID_JWK_X5C_ERROR,
22
22
  NotificationRequest,
23
23
  NotificationResult,
@@ -1,9 +1,9 @@
1
+ import { JWK } from '@sphereon/oid4vc-common';
1
2
  import {
2
3
  AccessTokenResponse,
3
4
  Alg,
4
5
  createProofOfPossession,
5
6
  EndpointMetadata,
6
- JWK,
7
7
  Jwt,
8
8
  NO_JWT_PROVIDED,
9
9
  OpenId4VCIVersion,
@@ -1,11 +1,4 @@
1
- import {
2
- AccessTokenRequest,
3
- AccessTokenResponse,
4
- GrantTypes,
5
- OpenIDResponse,
6
- PRE_AUTH_CODE_LITERAL,
7
- WellKnownEndpoints,
8
- } from '@sphereon/oid4vci-common';
1
+ import { AccessTokenRequest, AccessTokenResponse, GrantTypes, PRE_AUTH_CODE_LITERAL, WellKnownEndpoints } from '@sphereon/oid4vci-common';
9
2
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
10
3
  // @ts-ignore
11
4
  import nock from 'nock';
@@ -50,7 +43,7 @@ describe('AccessTokenClient should', () => {
50
43
  };
51
44
  nock(MOCK_URL).post(/.*/).reply(200, JSON.stringify(body));
52
45
 
53
- const accessTokenResponse: OpenIDResponse<AccessTokenResponse> = await accessTokenClient.acquireAccessTokenUsingRequest({
46
+ const accessTokenResponse = await accessTokenClient.acquireAccessTokenUsingRequest({
54
47
  accessTokenRequest,
55
48
  pinMetadata: {
56
49
  isPinRequired: true,
@@ -88,7 +81,7 @@ describe('AccessTokenClient should', () => {
88
81
  };
89
82
  nock(MOCK_URL).post(/.*/).reply(200, JSON.stringify(body));
90
83
 
91
- const accessTokenResponse: OpenIDResponse<AccessTokenResponse> = await accessTokenClient.acquireAccessTokenUsingRequest({
84
+ const accessTokenResponse = await accessTokenClient.acquireAccessTokenUsingRequest({
92
85
  accessTokenRequest,
93
86
  asOpts: { as: MOCK_URL },
94
87
  });
@@ -227,7 +220,7 @@ describe('AccessTokenClient should', () => {
227
220
  .post(/.*/)
228
221
  .reply(200, {});
229
222
 
230
- const response: OpenIDResponse<AccessTokenResponse> = await accessTokenClient.acquireAccessToken({
223
+ const response = await accessTokenClient.acquireAccessToken({
231
224
  credentialOffer: INITIATION_TEST,
232
225
  pin: '1234',
233
226
  });
@@ -1,12 +1,12 @@
1
1
  import * as crypto from 'crypto';
2
2
 
3
+ import { uuidv4 } from '@sphereon/oid4vc-common';
3
4
  import { Alg, Jwt, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-common';
4
5
  import { CredentialMapper } from '@sphereon/ssi-types';
5
6
  import * as didts from '@transmute/did-key.js';
6
7
  import { fetch } from 'cross-fetch';
7
8
  import debug from 'debug';
8
9
  import { importJWK, JWK, SignJWT } from 'jose';
9
- import { v4 } from 'uuid';
10
10
 
11
11
  import { OpenID4VCIClientV1_0_11 } from '..';
12
12
 
@@ -94,7 +94,7 @@ async function getCredentialOffer(format: 'ldp_vc' | 'jwt_vc_json'): Promise<Cre
94
94
  credentials: ['GuestCredential'],
95
95
  grants: {
96
96
  'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
97
- 'pre-authorized_code': v4().substring(0, 10),
97
+ 'pre-authorized_code': uuidv4().substring(0, 10),
98
98
  user_pin_required: false,
99
99
  },
100
100
  },
@@ -165,7 +165,7 @@ describe('ismapolis bug report #63, https://github.com/Sphereon-Opensource/OID4V
165
165
  format: 'jwt_vc_json',
166
166
  alg: Alg.ES256K,
167
167
  kid: didDocument.verificationMethod[0].id,
168
- jti: v4(),
168
+ jti: uuidv4(),
169
169
  });
170
170
  console.log(JSON.stringify(credentialResponse.credential));
171
171
  });
@@ -1,5 +1,5 @@
1
+ import { uuidv4 } from '@sphereon/oid4vc-common';
1
2
  import { AccessTokenRequest, AccessTokenRequestOpts, Jwt, OpenId4VCIVersion } from '@sphereon/oid4vci-common';
2
- import { v4 } from 'uuid';
3
3
 
4
4
  import { ProofOfPossessionBuilder } from '../ProofOfPossessionBuilder';
5
5
 
@@ -35,7 +35,7 @@ export const createJwtBearerClientAssertion = async (
35
35
  iss: clientId,
36
36
  sub: clientId,
37
37
  aud: credentialIssuer,
38
- jti: v4(),
38
+ jti: uuidv4(),
39
39
  exp: Date.now() / 1000 + 60,
40
40
  iat: Date.now() / 1000 - 60,
41
41
  },
@@ -0,0 +1,35 @@
1
+ import { dpopTokenRequestNonceError } from '@sphereon/oid4vc-common';
2
+ import { OpenIDResponse } from 'oid4vci-common';
3
+
4
+ export type RetryRequestWithDPoPNonce = { ok: true; dpopNonce: string } | { ok: false };
5
+
6
+ export function shouldRetryTokenRequestWithDPoPNonce(response: OpenIDResponse<unknown, unknown>): RetryRequestWithDPoPNonce {
7
+ if (!response.errorBody || response.errorBody.error !== dpopTokenRequestNonceError) {
8
+ return { ok: false };
9
+ }
10
+
11
+ const dPoPNonce = response.origResponse.headers.get('DPoP-Nonce');
12
+ if (!dPoPNonce) {
13
+ throw new Error('Missing required DPoP-Nonce header.');
14
+ }
15
+
16
+ return { ok: true, dpopNonce: dPoPNonce };
17
+ }
18
+
19
+ export function shouldRetryResourceRequestWithDPoPNonce(response: OpenIDResponse<unknown, unknown>): RetryRequestWithDPoPNonce {
20
+ if (!response.errorBody || response.origResponse.status !== 401) {
21
+ return { ok: false };
22
+ }
23
+
24
+ const wwwAuthenticateHeader = response.origResponse.headers.get('WWW-Authenticate');
25
+ if (!wwwAuthenticateHeader?.includes(dpopTokenRequestNonceError)) {
26
+ return { ok: false };
27
+ }
28
+
29
+ const dPoPNonce = response.origResponse.headers.get('DPoP-Nonce');
30
+ if (!dPoPNonce) {
31
+ throw new Error('Missing required DPoP-Nonce header.');
32
+ }
33
+
34
+ return { ok: true, dpopNonce: dPoPNonce };
35
+ }
@@ -19,12 +19,10 @@ export async function sendNotification(
19
19
  const error = response.errorBody?.error !== undefined;
20
20
  const result = {
21
21
  error,
22
- response: error ? await response.errorBody?.json() : undefined,
22
+ response: error ? response.errorBody : undefined,
23
23
  };
24
24
  if (error) {
25
- LOG.warning(
26
- `Notification endpoint returned an error for event '${request.event}' and id ${request.notification_id}: ${await response.errorBody?.json()}`,
27
- );
25
+ LOG.warning(`Notification endpoint returned an error for event '${request.event}' and id ${request.notification_id}: ${response.errorBody}`);
28
26
  } else {
29
27
  LOG.debug(`Notification endpoint returned success for event '${request.event}' and id ${request.notification_id}`);
30
28
  }