@sphereon/oid4vci-client 0.15.2-unstable.8 → 0.16.1-next.13

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 (83) hide show
  1. package/dist/AccessTokenClient.d.ts +7 -5
  2. package/dist/AccessTokenClient.d.ts.map +1 -1
  3. package/dist/AccessTokenClient.js +25 -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 +3 -2
  11. package/dist/AuthorizationCodeClient.js.map +1 -1
  12. package/dist/AuthorizationCodeClientV1_0_11.js +1 -1
  13. package/dist/AuthorizationCodeClientV1_0_11.js.map +1 -1
  14. package/dist/CredentialRequestClient.d.ts +6 -4
  15. package/dist/CredentialRequestClient.d.ts.map +1 -1
  16. package/dist/CredentialRequestClient.js +23 -5
  17. package/dist/CredentialRequestClient.js.map +1 -1
  18. package/dist/CredentialRequestClientV1_0_11.d.ts +5 -3
  19. package/dist/CredentialRequestClientV1_0_11.d.ts.map +1 -1
  20. package/dist/CredentialRequestClientV1_0_11.js +32 -4
  21. package/dist/CredentialRequestClientV1_0_11.js.map +1 -1
  22. package/dist/MetadataClient.d.ts +1 -0
  23. package/dist/MetadataClient.d.ts.map +1 -1
  24. package/dist/MetadataClient.js +5 -4
  25. package/dist/MetadataClient.js.map +1 -1
  26. package/dist/MetadataClientV1_0_13.d.ts +1 -0
  27. package/dist/MetadataClientV1_0_13.d.ts.map +1 -1
  28. package/dist/MetadataClientV1_0_13.js +5 -9
  29. package/dist/MetadataClientV1_0_13.js.map +1 -1
  30. package/dist/OpenID4VCIClient.d.ts +10 -10
  31. package/dist/OpenID4VCIClient.d.ts.map +1 -1
  32. package/dist/OpenID4VCIClient.js +11 -17
  33. package/dist/OpenID4VCIClient.js.map +1 -1
  34. package/dist/OpenID4VCIClientV1_0_11.d.ts +10 -9
  35. package/dist/OpenID4VCIClientV1_0_11.d.ts.map +1 -1
  36. package/dist/OpenID4VCIClientV1_0_11.js +14 -7
  37. package/dist/OpenID4VCIClientV1_0_11.js.map +1 -1
  38. package/dist/OpenID4VCIClientV1_0_13.d.ts +10 -9
  39. package/dist/OpenID4VCIClientV1_0_13.d.ts.map +1 -1
  40. package/dist/OpenID4VCIClientV1_0_13.js +11 -6
  41. package/dist/OpenID4VCIClientV1_0_13.js.map +1 -1
  42. package/dist/ProofOfPossessionBuilder.d.ts +2 -1
  43. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
  44. package/dist/ProofOfPossessionBuilder.js.map +1 -1
  45. package/dist/functions/AccessTokenUtil.d.ts.map +1 -1
  46. package/dist/functions/AccessTokenUtil.js +2 -2
  47. package/dist/functions/AccessTokenUtil.js.map +1 -1
  48. package/dist/functions/dpopUtil.d.ts +10 -0
  49. package/dist/functions/dpopUtil.d.ts.map +1 -0
  50. package/dist/functions/dpopUtil.js +30 -0
  51. package/dist/functions/dpopUtil.js.map +1 -0
  52. package/dist/functions/notifications.d.ts.map +1 -1
  53. package/dist/functions/notifications.js +3 -3
  54. package/dist/functions/notifications.js.map +1 -1
  55. package/dist/index.d.ts +0 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +0 -1
  58. package/dist/index.js.map +1 -1
  59. package/lib/AccessTokenClient.ts +44 -7
  60. package/lib/AccessTokenClientV1_0_11.ts +44 -7
  61. package/lib/AuthorizationCodeClient.ts +4 -3
  62. package/lib/AuthorizationCodeClientV1_0_11.ts +3 -3
  63. package/lib/CredentialRequestClient.ts +49 -7
  64. package/lib/CredentialRequestClientV1_0_11.ts +48 -5
  65. package/lib/MetadataClient.ts +2 -1
  66. package/lib/MetadataClientV1_0_13.ts +2 -7
  67. package/lib/OpenID4VCIClient.ts +26 -28
  68. package/lib/OpenID4VCIClientV1_0_11.ts +25 -14
  69. package/lib/OpenID4VCIClientV1_0_13.ts +23 -13
  70. package/lib/ProofOfPossessionBuilder.ts +1 -1
  71. package/lib/__tests__/AccessTokenClient.spec.ts +4 -11
  72. package/lib/__tests__/SphereonE2E.spec.test.ts +3 -3
  73. package/lib/functions/AccessTokenUtil.ts +2 -2
  74. package/lib/functions/dpopUtil.ts +35 -0
  75. package/lib/functions/notifications.ts +2 -4
  76. package/lib/index.ts +0 -1
  77. package/package.json +5 -4
  78. package/dist/IssuerSessionClient.d.ts +0 -3
  79. package/dist/IssuerSessionClient.d.ts.map +0 -1
  80. package/dist/IssuerSessionClient.js +0 -28
  81. package/dist/IssuerSessionClient.js.map +0 -1
  82. package/lib/IssuerSessionClient.ts +0 -17
  83. package/lib/__tests__/IssuerSessionClient.spec.ts +0 -64
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,eAAO,MAAM,GAAG,EAAE,aAAa,CAAC,MAAM,CAA8C,CAAC;AAErF,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,kCAAkC,CAAC;AACjD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,eAAO,MAAM,GAAG,EAAE,aAAa,CAAC,MAAM,CAA8C,CAAC;AAErF,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,kCAAkC,CAAC;AACjD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -36,6 +36,5 @@ __exportStar(require("./MetadataClientV1_0_11"), exports);
36
36
  __exportStar(require("./OpenID4VCIClient"), exports);
37
37
  __exportStar(require("./OpenID4VCIClientV1_0_13"), exports);
38
38
  __exportStar(require("./OpenID4VCIClientV1_0_11"), exports);
39
- __exportStar(require("./IssuerSessionClient"), exports);
40
39
  __exportStar(require("./ProofOfPossessionBuilder"), exports);
41
40
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6DAAuD;AAG1C,QAAA,GAAG,GAA0B,4BAAW,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;AAErF,sDAAoC;AACpC,6DAA2C;AAC3C,4DAA0C;AAC1C,mEAAiD;AACjD,4DAA0C;AAC1C,0DAAwC;AACxC,iEAA+C;AAC/C,iEAA+C;AAC/C,mEAAiD;AACjD,mEAAiD;AACjD,0EAAwD;AACxD,0EAAwD;AACxD,8CAA4B;AAC5B,mDAAiC;AACjC,0DAAwC;AACxC,0DAAwC;AACxC,qDAAmC;AACnC,4DAA0C;AAC1C,4DAA0C;AAC1C,wDAAsC;AACtC,6DAA2C"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6DAAuD;AAG1C,QAAA,GAAG,GAA0B,4BAAW,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;AAErF,sDAAoC;AACpC,6DAA2C;AAC3C,4DAA0C;AAC1C,mEAAiD;AACjD,4DAA0C;AAC1C,0DAAwC;AACxC,iEAA+C;AAC/C,iEAA+C;AAC/C,mEAAiD;AACjD,mEAAiD;AACjD,0EAAwD;AACxD,0EAAwD;AACxD,8CAA4B;AAC5B,mDAAiC;AACjC,0DAAwC;AACxC,0DAAwC;AACxC,qDAAmC;AACnC,4DAA0C;AAC1C,4DAA0C;AAC1C,6DAA2C"}
@@ -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
+ ...(nextDPoPNonce && { params: { 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
@@ -208,6 +238,7 @@ export class AccessTokenClient {
208
238
  throw new Error('Authorization flow requires the code to be present');
209
239
  }
210
240
  }
241
+
211
242
  private validate(accessTokenRequest: AccessTokenRequest, pinMeta?: TxCodeAndPinRequired): void {
212
243
  if (accessTokenRequest.grant_type === GrantTypes.PRE_AUTHORIZED_CODE) {
213
244
  this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type);
@@ -222,8 +253,14 @@ export class AccessTokenClient {
222
253
  }
223
254
  }
224
255
 
225
- private async sendAuthCode(requestTokenURL: string, accessTokenRequest: AccessTokenRequest): Promise<OpenIDResponse<AccessTokenResponse>> {
226
- return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }));
256
+ private async sendAuthCode(
257
+ requestTokenURL: string,
258
+ accessTokenRequest: AccessTokenRequest,
259
+ opts?: { headers?: Record<string, string> },
260
+ ): Promise<OpenIDResponse<AccessTokenResponse, DPoPResponseParams>> {
261
+ return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }), {
262
+ customHeaders: opts?.headers ? opts.headers : undefined,
263
+ });
227
264
  }
228
265
 
229
266
  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
+ ...(nextDPoPNonce && { params: { 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)
@@ -190,6 +220,7 @@ export class AccessTokenClientV1_0_11 {
190
220
  throw new Error('Authorization flow requires the code to be present');
191
221
  }
192
222
  }
223
+
193
224
  private validate(accessTokenRequest: AccessTokenRequest, isPinRequired?: boolean): void {
194
225
  if (accessTokenRequest.grant_type === GrantTypes.PRE_AUTHORIZED_CODE) {
195
226
  this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type);
@@ -204,8 +235,14 @@ export class AccessTokenClientV1_0_11 {
204
235
  }
205
236
  }
206
237
 
207
- private async sendAuthCode(requestTokenURL: string, accessTokenRequest: AccessTokenRequest): Promise<OpenIDResponse<AccessTokenResponse>> {
208
- return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }));
238
+ private async sendAuthCode(
239
+ requestTokenURL: string,
240
+ accessTokenRequest: AccessTokenRequest,
241
+ opts?: { headers?: Record<string, string> },
242
+ ): Promise<OpenIDResponse<AccessTokenResponse>> {
243
+ return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }), {
244
+ customHeaders: opts?.headers ? opts.headers : undefined,
245
+ });
209
246
  }
210
247
 
211
248
  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
@@ -148,8 +148,9 @@ export const createAuthorizationRequestUrl = async ({
148
148
 
149
149
  // SD-JWT VC
150
150
  const vct = cred.format === 'vc+sd-jwt' ? cred.vct : undefined;
151
+ const doctype = cred.format === 'mso_mdoc' ? cred.doctype : undefined;
151
152
 
152
- // W3C credentials
153
+ // W3C credentials have a credential definition, the rest does not
153
154
  let credential_definition: undefined | Partial<CredentialDefinitionJwtVcJsonV1_0_13 | CredentialDefinitionJwtVcJsonLdAndLdpVcV1_0_13> =
154
155
  undefined;
155
156
  if (isW3cCredentialSupported(cred)) {
@@ -171,6 +172,7 @@ export const createAuthorizationRequestUrl = async ({
171
172
  ...(credential_configuration_id && { credential_configuration_id }),
172
173
  ...(format && { format }),
173
174
  ...(vct && { vct, claims: cred.claims ? removeDisplayAndValueTypes(cred.claims) : undefined }),
175
+ ...(doctype && { doctype, claims: cred.claims ? removeDisplayAndValueTypes(cred.claims) : undefined }),
174
176
  } as AuthorizationDetails;
175
177
  });
176
178
  if (!authorizationDetails || authorizationDetails.length === 0) {
@@ -182,7 +184,6 @@ export const createAuthorizationRequestUrl = async ({
182
184
  }
183
185
  const parEndpoint = authorizationMetadata?.pushed_authorization_request_endpoint;
184
186
 
185
-
186
187
  let queryObj: Record<string, any> | PushedAuthorizationResponse = {
187
188
  response_type: ResponseType.AUTH_CODE,
188
189
  ...(!pkce.disabled && {
@@ -4,7 +4,7 @@ import {
4
4
  CodeChallengeMethod,
5
5
  convertJsonToURI,
6
6
  CreateRequestObjectMode,
7
- CredentialOfferFormat,
7
+ CredentialOfferFormatV1_0_11,
8
8
  CredentialOfferPayloadV1_0_11,
9
9
  CredentialOfferRequestWithBaseUrl,
10
10
  CredentialsSupportedLegacy,
@@ -40,14 +40,14 @@ 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) {
47
47
  if (!credentialOffer) {
48
48
  throw Error('Please provide a scope or authorization_details if no credential offer is present');
49
49
  }
50
- const creds: (CredentialOfferFormat | string)[] = (credentialOffer.credential_offer as CredentialOfferPayloadV1_0_11).credentials;
50
+ const creds: (CredentialOfferFormatV1_0_11 | string)[] = (credentialOffer.credential_offer as CredentialOfferPayloadV1_0_11).credentials;
51
51
 
52
52
  // FIXME: complains about VCT for sd-jwt
53
53
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -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,
@@ -14,13 +16,14 @@ import {
14
16
  UniformCredentialRequest,
15
17
  URL_NOT_VALID,
16
18
  } from '@sphereon/oid4vci-common';
17
- import { ExperimentalSubjectIssuance } from '@sphereon/oid4vci-common/dist/experimental/holder-vci';
19
+ import { ExperimentalSubjectIssuance } from '@sphereon/oid4vci-common';
18
20
  import { CredentialFormat } from '@sphereon/ssi-types';
19
21
  import Debug from 'debug';
20
22
 
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
+ ...(dPoP && { customHeaders: { 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
+ ...(createDPoPOpts && { customHeaders: { 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
+ ...(nextDPoPNonce && { params: { dpop: { dpopNonce: nextDPoPNonce } } }),
170
+ };
138
171
  }
139
172
 
140
173
  public async acquireDeferredCredential(
@@ -228,13 +261,22 @@ export class CredentialRequestClient {
228
261
  if (types.length > 1) {
229
262
  throw Error(`Only a single credential type is supported for ${format}`);
230
263
  }
231
- // fixme: this isn't up to the CredentialRequest that we see in the version v1_0_13
232
264
  return {
233
265
  format,
234
266
  proof,
235
267
  vct: types[0],
236
268
  ...opts.subjectIssuance,
237
269
  };
270
+ } else if (format === 'mso_mdoc') {
271
+ if (types.length > 1) {
272
+ throw Error(`Only a single credential type is supported for ${format}`);
273
+ }
274
+ return {
275
+ format,
276
+ proof,
277
+ doctype: types[0],
278
+ ...opts.subjectIssuance,
279
+ };
238
280
  }
239
281
 
240
282
  throw new Error(`Unsupported format: ${format}`);
@@ -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
+ ...(nextDPoPNonce && { params: { dpop: { dpopNonce: nextDPoPNonce } } }),
129
+ };
97
130
  }
98
131
 
99
132
  public async acquireDeferredCredential(
@@ -182,6 +215,16 @@ export class CredentialRequestClientV1_0_11 {
182
215
  proof,
183
216
  vct: types[0],
184
217
  };
218
+ } else if (format === 'mso_mdoc') {
219
+ if (types.length > 1) {
220
+ throw Error(`Only a single credential type is supported for ${format}`);
221
+ }
222
+
223
+ return {
224
+ format,
225
+ proof,
226
+ doctype: types[0],
227
+ };
185
228
  }
186
229
 
187
230
  throw new Error(`Unsupported format: ${format}`);
@@ -19,7 +19,7 @@ import Debug from 'debug';
19
19
 
20
20
  import { MetadataClientV1_0_11 } from './MetadataClientV1_0_11';
21
21
  import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
22
- import { retrieveWellknown } from './functions/OpenIDUtils';
22
+ import { retrieveWellknown } from './functions';
23
23
 
24
24
  const debug = Debug('sphereon:oid4vci:metadata');
25
25
 
@@ -204,6 +204,7 @@ export class MetadataClient {
204
204
  * Retrieve only the OID4VCI metadata for the issuer. So no OIDC/OAuth2 metadata
205
205
  *
206
206
  * @param issuerHost The issuer hostname
207
+ * @param opts
207
208
  */
208
209
  public static async retrieveOpenID4VCIServerMetadata(
209
210
  issuerHost: string,
@@ -12,7 +12,7 @@ import {
12
12
  } from '@sphereon/oid4vci-common';
13
13
  import Debug from 'debug';
14
14
 
15
- import { retrieveWellknown } from './functions/OpenIDUtils';
15
+ import { retrieveWellknown } from './functions';
16
16
 
17
17
  const debug = Debug('sphereon:oid4vci:metadata');
18
18
 
@@ -50,7 +50,6 @@ export class MetadataClientV1_0_13 {
50
50
  let credential_endpoint: string | undefined;
51
51
  let deferred_credential_endpoint: string | undefined;
52
52
  let authorization_endpoint: string | undefined;
53
- let session_endpoint: string | undefined;
54
53
  let authorizationServerType: AuthorizationServerType = 'OID4VCI';
55
54
  let authorization_servers: string[] = [issuer];
56
55
  const oid4vciResponse = await MetadataClientV1_0_13.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); // We will handle errors later, given we will also try other metadata locations
@@ -158,16 +157,11 @@ export class MetadataClientV1_0_13 {
158
157
  credentialIssuerMetadata = authMetadata as CredentialIssuerMetadataV1_0_13;
159
158
  }
160
159
  debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`);
161
-
162
- if(credentialIssuerMetadata?.session_endpoint !== undefined) {
163
- session_endpoint = credentialIssuerMetadata.session_endpoint
164
- }
165
160
  return {
166
161
  issuer,
167
162
  token_endpoint,
168
163
  credential_endpoint,
169
164
  deferred_credential_endpoint,
170
- session_endpoint,
171
165
  authorization_server: authorization_servers[0],
172
166
  authorization_endpoint,
173
167
  authorizationServerType,
@@ -180,6 +174,7 @@ export class MetadataClientV1_0_13 {
180
174
  * Retrieve only the OID4VCI metadata for the issuer. So no OIDC/OAuth2 metadata
181
175
  *
182
176
  * @param issuerHost The issuer hostname
177
+ * @param opts
183
178
  */
184
179
  public static async retrieveOpenID4VCIServerMetadata(
185
180
  issuerHost: string,