@sphereon/oid4vci-client 0.10.4-unstable.61 → 0.10.4-unstable.7

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 (106) hide show
  1. package/README.md +514 -515
  2. package/dist/AccessTokenClient.d.ts +5 -5
  3. package/dist/AccessTokenClient.d.ts.map +1 -1
  4. package/dist/AccessTokenClient.js +25 -45
  5. package/dist/AccessTokenClient.js.map +1 -1
  6. package/dist/AuthorizationCodeClient.d.ts +5 -5
  7. package/dist/AuthorizationCodeClient.d.ts.map +1 -1
  8. package/dist/AuthorizationCodeClient.js +8 -19
  9. package/dist/AuthorizationCodeClient.js.map +1 -1
  10. package/dist/CredentialOfferClient.d.ts.map +1 -1
  11. package/dist/CredentialOfferClient.js +26 -14
  12. package/dist/CredentialOfferClient.js.map +1 -1
  13. package/dist/CredentialRequestClient.d.ts +8 -5
  14. package/dist/CredentialRequestClient.d.ts.map +1 -1
  15. package/dist/CredentialRequestClient.js +28 -16
  16. package/dist/CredentialRequestClient.js.map +1 -1
  17. package/dist/CredentialRequestClientBuilder.d.ts +7 -7
  18. package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
  19. package/dist/CredentialRequestClientBuilder.js +14 -13
  20. package/dist/CredentialRequestClientBuilder.js.map +1 -1
  21. package/dist/MetadataClient.d.ts +15 -5
  22. package/dist/MetadataClient.d.ts.map +1 -1
  23. package/dist/MetadataClient.js +33 -13
  24. package/dist/MetadataClient.js.map +1 -1
  25. package/dist/OpenID4VCIClient.d.ts +16 -9
  26. package/dist/OpenID4VCIClient.d.ts.map +1 -1
  27. package/dist/OpenID4VCIClient.js +91 -46
  28. package/dist/OpenID4VCIClient.js.map +1 -1
  29. package/dist/ProofOfPossessionBuilder.js +1 -1
  30. package/dist/ProofOfPossessionBuilder.js.map +1 -1
  31. package/dist/functions/ProofUtil.d.ts.map +1 -1
  32. package/dist/index.d.ts +1 -8
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -8
  35. package/dist/index.js.map +1 -1
  36. package/lib/AccessTokenClient.ts +245 -277
  37. package/lib/AuthorizationCodeClient.ts +163 -183
  38. package/lib/CredentialOfferClient.ts +112 -99
  39. package/lib/CredentialRequestClient.ts +208 -187
  40. package/lib/CredentialRequestClientBuilder.ts +155 -153
  41. package/lib/MetadataClient.ts +208 -188
  42. package/lib/OpenID4VCIClient.ts +629 -579
  43. package/lib/ProofOfPossessionBuilder.ts +204 -204
  44. package/lib/__tests__/AccessTokenClient.spec.ts +211 -239
  45. package/lib/__tests__/CredentialRequestClient.spec.ts +311 -342
  46. package/lib/__tests__/CredentialRequestClientBuilder.spec.ts +131 -137
  47. package/lib/__tests__/EBSIE2E.spec.test.ts +145 -145
  48. package/lib/__tests__/IT.spec.ts +208 -421
  49. package/lib/__tests__/IssuanceInitiation.spec.ts +83 -116
  50. package/lib/__tests__/JsonURIConversions.spec.ts +146 -146
  51. package/lib/__tests__/MattrE2E.spec.test.ts +104 -104
  52. package/lib/__tests__/MetadataClient.spec.ts +260 -311
  53. package/lib/__tests__/MetadataMocks.ts +444 -483
  54. package/lib/__tests__/OpenID4VCIClient.spec.ts +202 -224
  55. package/lib/__tests__/{OpenID4VCIClientPARV1_0_11.spec.ts → OpenID4VCIClientPAR.spec.ts} +122 -122
  56. package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +110 -110
  57. package/lib/__tests__/SdJwt.spec.ts +163 -167
  58. package/lib/__tests__/SphereonE2E.spec.test.ts +169 -169
  59. package/lib/__tests__/data/VciDataFixtures.ts +745 -1430
  60. package/lib/functions/AuthorizationUtil.ts +18 -18
  61. package/lib/functions/ProofUtil.ts +128 -128
  62. package/lib/index.ts +9 -16
  63. package/package.json +3 -3
  64. package/dist/AccessTokenClientV1_0_11.d.ts +0 -29
  65. package/dist/AccessTokenClientV1_0_11.d.ts.map +0 -1
  66. package/dist/AccessTokenClientV1_0_11.js +0 -212
  67. package/dist/AccessTokenClientV1_0_11.js.map +0 -1
  68. package/dist/AuthorizationCodeClientV1_0_11.d.ts +0 -9
  69. package/dist/AuthorizationCodeClientV1_0_11.d.ts.map +0 -1
  70. package/dist/AuthorizationCodeClientV1_0_11.js +0 -132
  71. package/dist/AuthorizationCodeClientV1_0_11.js.map +0 -1
  72. package/dist/CredentialOfferClientV1_0_11.d.ts +0 -10
  73. package/dist/CredentialOfferClientV1_0_11.d.ts.map +0 -1
  74. package/dist/CredentialOfferClientV1_0_11.js +0 -103
  75. package/dist/CredentialOfferClientV1_0_11.js.map +0 -1
  76. package/dist/CredentialRequestClientBuilderV1_0_11.d.ts +0 -46
  77. package/dist/CredentialRequestClientBuilderV1_0_11.d.ts.map +0 -1
  78. package/dist/CredentialRequestClientBuilderV1_0_11.js +0 -117
  79. package/dist/CredentialRequestClientBuilderV1_0_11.js.map +0 -1
  80. package/dist/CredentialRequestClientV1_0_11.d.ts +0 -44
  81. package/dist/CredentialRequestClientV1_0_11.d.ts.map +0 -1
  82. package/dist/CredentialRequestClientV1_0_11.js +0 -151
  83. package/dist/CredentialRequestClientV1_0_11.js.map +0 -1
  84. package/dist/MetadataClientV1_0_11.d.ts +0 -31
  85. package/dist/MetadataClientV1_0_11.d.ts.map +0 -1
  86. package/dist/MetadataClientV1_0_11.js +0 -182
  87. package/dist/MetadataClientV1_0_11.js.map +0 -1
  88. package/dist/OpenID4VCIClientV1_0_11.d.ts +0 -107
  89. package/dist/OpenID4VCIClientV1_0_11.d.ts.map +0 -1
  90. package/dist/OpenID4VCIClientV1_0_11.js +0 -462
  91. package/dist/OpenID4VCIClientV1_0_11.js.map +0 -1
  92. package/dist/functions/OpenIDUtils.d.ts +0 -12
  93. package/dist/functions/OpenIDUtils.d.ts.map +0 -1
  94. package/dist/functions/OpenIDUtils.js +0 -37
  95. package/dist/functions/OpenIDUtils.js.map +0 -1
  96. package/lib/AccessTokenClientV1_0_11.ts +0 -255
  97. package/lib/AuthorizationCodeClientV1_0_11.ts +0 -168
  98. package/lib/CredentialOfferClientV1_0_11.ts +0 -112
  99. package/lib/CredentialRequestClientBuilderV1_0_11.ts +0 -156
  100. package/lib/CredentialRequestClientV1_0_11.ts +0 -191
  101. package/lib/MetadataClientV1_0_11.ts +0 -189
  102. package/lib/OpenID4VCIClientV1_0_11.ts +0 -645
  103. package/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts +0 -316
  104. package/lib/__tests__/IssuanceInitiationV1_0_11.spec.ts +0 -62
  105. package/lib/__tests__/OpenID4VCIClientV1_0_11.spec.ts +0 -202
  106. package/lib/functions/OpenIDUtils.ts +0 -25
package/README.md CHANGED
@@ -1,515 +1,514 @@
1
- <h1 align="center">
2
- <br>
3
- <a href="https://www.sphereon.com"><img src="https://sphereon.com/content/themes/sphereon/assets/img/logo.svg" alt="Sphereon" width="400"></a>
4
- <br>OpenID for Verifiable Credential Issuance - Client
5
- <br>
6
- </h1>
7
-
8
- [![CI](https://github.com/Sphereon-Opensource/openid4vci-client/actions/workflows/main.yml/badge.svg)](https://github.com/Sphereon-Opensource/openid4vci-client/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/Sphereon-Opensource/openid4vci-client/branch/develop/graph/badge.svg)](https://codecov.io/gh/Sphereon-Opensource/openid4vci-client) [![NPM Version](https://img.shields.io/npm/v/@sphereon/oid4vci-client.svg)](https://npm.im/@sphereon/oid4vci-client)
9
-
10
- _IMPORTANT this package is in an early development stage and currently only supports the pre-authorized code flow of
11
- OpenID4VCI!_
12
-
13
- # Background
14
-
15
- A client to request and receive Verifiable Credentials using
16
- the [OpenID for Verifiable Credential Issuance](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) (
17
- OpenID4VCI) specification for receiving Verifiable Credentials as a holder/subject.
18
-
19
- OpenID4VCI defines an API designated as Credential Endpoint that is used to issue verifiable credentials and
20
- corresponding OAuth 2.0 based authorization mechanisms (see [RFC6749]) that a Wallet uses to obtain authorization to
21
- receive verifiable credentials. W3C formats as well as other Credential formats are supported. This allows existing
22
- OAuth 2.0 deployments and OpenID Connect OPs (see [OpenID.Core]) to extend their service and become Credential Issuers.
23
- It also allows new applications built using Verifiable Credentials to utilize OAuth 2.0 as integration and
24
- interoperability layer. This package provides holder/wallet support to interact with OpenID4VCI capable Issuer systems.
25
-
26
- # Flows
27
-
28
- The spec lists 2 flows. Currently only one is supported!
29
-
30
- ## Authorized Code Flow
31
-
32
- This flow isn't supported yet!
33
-
34
- ## Pre-authorized Code Flow
35
-
36
- The pre-authorized code flow assumes the user is using an out-of-band mechanism outside the issuance flow to
37
- authenticate first.
38
-
39
- The below diagram shows the steps involved in the pre-authorized code flow. Note that wallet inner functionalities (like
40
- saving VCs) are out of scope for this library.
41
-
42
- ![Flow diagram](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/Sphereon-Opensource/OID4VCI-client/develop/docs/preauthorized-code-flow.puml)
43
-
44
- # OpenID4VCI Client
45
-
46
- The OpenID4VCI client is the main client you typically will want to use. It combines several lower level classes into a
47
- client you can use to finish the pre-authorized code flows.
48
-
49
- ## Initiating the client
50
-
51
- This initiates the client using a URI obtained from the Issuer using a link (URL) or QR code typically. We are also
52
- already fetching the Server Metadata
53
-
54
- Using openid-initiate-issuance scheme
55
-
56
- ```typescript
57
- import { OpenID4VCIClient } from '@sphereon/oid4vci-client';
58
-
59
- // The client is initiated from a URI. This URI is provided by the Issuer, typically as a URL or QR code.
60
- const client = await OpenID4VCIClient.fromURI({
61
- uri: 'openid-initiate-issuance://?issuer=https%3A%2F%2Fissuer.research.identiproof.io&credential_type=OpenBadgeCredentialUrl&pre-authorized_code=4jLs9xZHEfqcoow0kHE7d1a8hUk6Sy-5bVSV2MqBUGUgiFFQi-ImL62T-FmLIo8hKA1UdMPH0lM1xAgcFkJfxIw9L-lI3mVs0hRT8YVwsEM1ma6N3wzuCdwtMU4bcwKp&user_pin_required=true',
62
- kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#key-1', // Our DID. You can defer this also to when the acquireCredential method is called
63
- alg: Alg.ES256, // The signing Algorithm we will use. You can defer this also to when the acquireCredential method is called
64
- clientId: 'test-clientId', // The clientId if the Authrozation Service requires it. If a clientId is needed you can defer this also to when the acquireAccessToken method is called
65
- retrieveServerMetadata: true, // Already retrieve the server metadata. Can also be done afterwards by invoking a method yourself.
66
- });
67
-
68
- console.log(client.getIssuer()); // https://issuer.research.identiproof.io
69
- console.log(client.getCredentialEndpoint()); // https://issuer.research.identiproof.io/credential
70
- console.log(client.getAccessTokenEndpoint()); // https://auth.research.identiproof.io/oauth2/token
71
- ```
72
-
73
- Using https scheme
74
-
75
- ```typescript
76
- import { OpenID4VCIClient } from '@sphereon/oid4vci-client';
77
-
78
- // The client is initiated from a URI. This URI is provided by the Issuer, typically as a URL or QR code.
79
- const client = await OpenID4VCIClient.fromURI({
80
- uri: 'https://launchpad.vii.electron.mattrlabs.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Flaunchpad.vii.electron.mattrlabs.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22types%22%3A%5B%22OpenBadgeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X%22%7D%7D%7D',
81
- kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#key-1', // Our DID. You can defer this also to when the acquireCredential method is called
82
- alg: Alg.ES256, // The signing Algorithm we will use. You can defer this also to when the acquireCredential method is called
83
- clientId: 'test-clientId', // The clientId if the Authrozation Service requires it. If a clientId is needed you can defer this also to when the acquireAccessToken method is called
84
- retrieveServerMetadata: true, // Already retrieve the server metadata. Can also be done afterwards by invoking a method yourself.
85
- });
86
-
87
- console.log(client.getIssuer()); // https://launchpad.vii.electron.mattrlabs.io
88
- console.log(client.getCredentialEndpoint()); // https://launchpad.vii.electron.mattrlabs.io/credential
89
- console.log(client.getAccessTokenEndpoint()); // https://launchpad.vii.electron.mattrlabs.io/oauth2/token
90
- ```
91
-
92
- ## Server metadata
93
-
94
- The OID4VCI Server metadata contains information about token endpoints, credential endpoints, as well as additional
95
- information about supported Credentials, and their cryptographic suites and formats.
96
- The code above already retrieved the metadata, so it will not be fetched again, and this method places the data in another variable. If you however have not used
97
- the `retrieveServerMetadata` option, you can use this method to fetch it from the Issuer:
98
-
99
- ```typescript
100
- const metadata = await client.retrieveServerMetadata();
101
- ```
102
-
103
- ## Access token from Authorization Server
104
-
105
- Next we need to get an Access token from the OAuth2 Authorization Server using the token endpoint. This endpoint is
106
- found from the metadata if the server supports it. Otherwise a default location based on the issuer value from the
107
- Initiate Issuance Request is used.
108
-
109
- ```typescript
110
- const accessToken = await client.acquireAccessToken({ pin: '1234' });
111
- console.log(accessToken);
112
- /**
113
- * {
114
- * access_token: 'ey6546.546654.64565',
115
- * authorization_pending: false,
116
- * c_nonce: 'c_nonce2022101300',
117
- * c_nonce_expires_in: 2025101300,
118
- * interval: 2025101300,
119
- * token_type: 'Bearer',
120
- * }
121
- */
122
- ```
123
-
124
- ## Getting the credential
125
-
126
- Now it is time to get the credential. In order to achieve this, we will be using the metadata together with the access
127
- token, but first we will have to create a so-called Proof of Possession. Please see
128
- the [Proof of Posession](#proof-of-possession) chapter for more information.
129
-
130
- The Proof of Possession using a signature callback function. The example uses the `jose` library.
131
-
132
- ```typescript
133
- import * as jose from 'jose';
134
- import { DIDDocument } from 'did-resolver';
135
-
136
- const { privateKey, publicKey } = await jose.generateKeyPair('ES256');
137
-
138
- // Must be JWS
139
- async function signCallback(args: Jwt, kid: string): Promise<string> {
140
- return await new jose.SignJWT({ ...args.payload })
141
- .setProtectedHeader({ alg: args.header.alg })
142
- .setIssuedAt()
143
- .setIssuer(kid)
144
- .setAudience(args.payload.aud)
145
- .setExpirationTime('2h')
146
- .sign(privateKey);
147
- }
148
-
149
- const callbacks: ProofOfPossessionCallbacks<DIDDocument> = {
150
- signCallback,
151
- };
152
- ```
153
-
154
- Now it is time to get the actual credential
155
-
156
- ```typescript
157
- const credentialResponse = await client.acquireCredentials({
158
- credentialTypes: 'OpenBadgeCredential',
159
- proofCallbacks: callbacks,
160
- format: 'jwt_vc_json',
161
- alg: Alg.ES256K,
162
- kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1',
163
- });
164
- console.log(credentialResponse.credential);
165
- // JWT format. (LDP / JSON-LD ('ldp_vc' / 'jwt_vc_json-ld') is also supported by the client)
166
- // eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly9leGFtcGxlLmVkdS9pc3N1ZXJzLzU2NTA0OSIsImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMDA6MDA6MDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.z5vgMTK1nfizNCg5N-niCOL3WUIAL7nXy-nGhDZYO_-PNGeE-0djCpWAMH8fD8eWSID5PfkPBYkx_dfLJnQ7NA
167
- ```
168
-
169
- # Using individual classes and methods instead of the client
170
-
171
- Instead of using the OpenID4VCI Client, you can also use the separate classes if you want. This typically gives you a
172
- bit more control and options, at the expense of a bit more complexity.
173
-
174
- ## Issuance Initiation
175
-
176
- Issuance is started from a so-called Issuance Initiation Request by the Issuer. This typically is URI, exposed
177
- as a link or a QR code. You can call the `CredentialOffer.fromURI(uri)` method to parse the URI into a Json object
178
- containing the baseUrl and a `uri` JSON object
179
-
180
- ```typescript
181
- import { CredentialOffer } from '@sphereon/oid4vci-client';
182
-
183
- const initiationURI =
184
- 'https://issuer.example.com?issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FdriverLicense&op_state=eyJhbGciOiJSU0Et...FYUaBy';
185
-
186
- const initiationRequestWithUrl = CredentialOffer.fromURI(initiationURI);
187
- console.log(initiationRequestWithUrl);
188
-
189
- /**
190
- * {
191
- * "baseUrl": "https://server.example.com",
192
- * "request": {
193
- * "credential_type": [
194
- * "https://did.example.org/healthCard",
195
- * "https://did.example.org/driverLicense"
196
- * ],
197
- * "issuer": "https://server.example.com",
198
- * "op_state": "eyJhbGciOiJSU0Et...FYUaBy"
199
- * },
200
- * "version": 9
201
- * }
202
- */
203
- ```
204
-
205
- ## Getting OpenID4VCI Server and OIDC/OAuth2 metadata
206
-
207
- The OpenID4VCI spec defines a server metadata object that contains information about the issuer and the credentials they
208
- support. Next to this predefined endpoint there are also the well-known locations for OpenID Connect Discovery
209
- configuration and
210
- Oauth2 Authorization Server configuration. These contain for instance the token endpoints.
211
- The MetadataClient checks the OpenID4VCI well-known location for the medata and existence of a token endpoint. If the
212
- OpenID4VCI well-known location is not found, the OIDC/OAuth2 well-known locations will be tried:
213
-
214
- Example:
215
-
216
- ```typescript
217
- import { MetadataClient } from '@sphereon/oid4vci-client';
218
-
219
- const metadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);
220
-
221
- console.log(metadata);
222
- /**
223
- * {
224
- * issuer: 'https://server.example.com',
225
- * credential_endpoint: 'https://server.example.com/credential',
226
- * token_endpoint: 'https://server.example.com/token',
227
- * jwks_uri: 'https://server.example.com/jwks',
228
- * grant_types_supported: ['urn:ietf:params:oauth:grant-type:pre-authorized_code'],
229
- * credentials_supported: {
230
- * OpenBadgeCredential: {
231
- * formats: {
232
- * jwt_vc: {
233
- * types: [
234
- * 'https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential',
235
- * 'https://w3id.org/ngi/OpenBadgeExtendedCredential',
236
- * ],
237
- * binding_methods_supported: ['did'],
238
- * cryptographic_suites_supported: ['ES256'],
239
- * },
240
- * },
241
- * },
242
- * },
243
- * }
244
- */
245
- ```
246
-
247
- ## Acquiring the Access Token
248
-
249
- Now you will need to get an access token from the oAuth2 Authorization Server (AS), using some values from
250
- the `IssuanceInitiationRequestPayloadV9` payload.
251
- For now, you can use the issuer hostname for the AS, as there is no way to know the AS from the Issuance Initiation for
252
- known until the
253
- following [OpenID Ticket](https://bitbucket.org/openid/connect/issues/1632/issuer-metadata-clarification-needed) is
254
- resolved. So the token endpoint would become https://<issuer-hostname>/token.
255
- The library allows to pass in a different value for the AS token endpoint as well, so you already can use a different AS
256
- if you know the AS upfront. If no AS is provided the issuer value from the Issuance Initiation Request will be used.
257
-
258
- ```typescript
259
- import { AccessTokenClient, AuthorizationServerOpts } from '@sphereon/oid4vci-client';
260
-
261
- const clientId = 'abcd'; // This can be a random value or a clientId assigned by the Authorization Server (depends on the environment)
262
- const pin = 1234; // A pincode which is shown out of band typically. Only use when the pin-code is required from the Issuance Initiation object.
263
-
264
- // Allows to override the Authorization Server and provide other AS options. By default the issuer value will be used
265
- const asOpts: AuthorizationServerOpts = {
266
- clientId,
267
- };
268
-
269
- const accessTokenResponse = AccessTokenClient.acquireAccessTokenUsingRequest({
270
- credentialOffer,
271
- asOpts,
272
- pin,
273
- metadata,
274
- });
275
- console.log(accessTokenResponse);
276
- /**
277
- * {
278
- * access_token: "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ"
279
- * token_type: "bearer",
280
- * expires_in: 86400
281
- * }
282
- */
283
- ```
284
-
285
- # Proof of Possession
286
-
287
- Part of OpenID4VCI is the holder showing that they are in possession of a certain key, associated with the DID that will
288
- be the subject of the to be issued Verifiable Credential.
289
- This proof of possession will be created using a DID, it's associated keypair and the `ProofOfPossessionBuilder` class.
290
- This Builder can be initiated from a JWT object if you want to create a JWT yourself, or it can be build using the
291
- Initiate Issuance Request, Server metadata and some methods from the builder. Both approaches need a callback function
292
- to sign the JWT and optionally a callback to verify the JWT.
293
- The signature of the callback functions you need to implement are:
294
-
295
- ```typescript
296
- export type JWTSignerCallback = (jwt: Jwt, kid: string) => Promise<string>;
297
- export type JWTVerifyCallback = (args: { jwt: string; kid: string }) => Promise<void>;
298
- ```
299
-
300
- This is an example of the signature callback function created using the `jose` library.
301
-
302
- ```typescript
303
- import { Jwt } from '@sphereon/oid4vci-client';
304
-
305
- const { privateKey, publicKey } = await jose.generateKeyPair('ES256');
306
-
307
- // Must be JWS
308
- async function signCallback(args: Jwt, kid: string): Promise<string> {
309
- return await new jose.SignJWT({ ...args.payload })
310
- .setProtectedHeader({ alg: args.header.alg })
311
- .setIssuedAt()
312
- .setIssuer(kid)
313
- .setAudience(args.payload.aud)
314
- .setExpirationTime('2h')
315
- .sign(keypair.privateKey);
316
- }
317
- ```
318
-
319
- Alongside signing, you can optionally provide another callback function for verifying the created signature with
320
- populating `verifyCallback`. The method is expected to throw errors in case problems with the JWT or it's signature are
321
- found.
322
- below is an example of such method. This example (like the previous one) uses `jose` to verify the jwt.
323
-
324
- ```typescript
325
- async function verifyCallback(args: { jwt: string; kid: string }): Promise<void> {
326
- await jose.compactVerify(args.jwt, keypair.publicKey);
327
- }
328
- ```
329
-
330
- Some important interface around Proof of Possession:
331
-
332
- ```typescript
333
- export enum Alg {
334
- EdDSA = 'EdDSA',
335
- ES256 = 'ES256',
336
- ES256K = 'ES256K',
337
- }
338
-
339
- export interface JWTHeader {
340
- alg: Alg; // REQUIRED by the JWT signer
341
- typ?: string; //JWT always
342
- kid?: string; // CONDITIONAL. JWT header containing the key ID. If the Credential shall be bound to a DID, the kid refers to a DID URL which identifies a particular key in the DID Document that the Credential shall be bound to. MUST NOT be present if jwk or x5c is present.
343
- jwk?: JWK; // CONDITIONAL. JWT header containing the key material the new Credential shall be bound to. MUST NOT be present if kid or x5c is present.
344
- x5c?: string[]; // CONDITIONAL. JWT header containing a certificate or certificate chain corresponding to the key used to sign the JWT. This element may be used to convey a key attestation. In such a case, the actual key certificate will contain attributes related to the key properties. MUST NOT be present if kid or jwk is present.
345
- }
346
-
347
- export interface JWTPayload {
348
- iss?: string; // REQUIRED (string). The value of this claim MUST be the client_id of the client making the credential request.
349
- aud?: string; // REQUIRED (string). The value of this claim MUST be the issuer URL of credential issuer.
350
- iat?: number; // REQUIRED (number). The value of this claim MUST be the time at which the proof was issued using the syntax defined in [RFC7519].
351
- nonce?: string; // REQUIRED (string). The value type of this claim MUST be a string, where the value is a c_nonce provided by the credential issuer. //TODO: Marked as required not present in NGI flow
352
- jti?: string; // A new nonce chosen by the wallet. Used to prevent replay
353
- exp?: number; // Not longer than 5 minutes
354
- }
355
-
356
- export interface Jwt {
357
- header?: JWTHeader;
358
- payload?: JWTPayload;
359
- }
360
- ```
361
-
362
- The arguments requested by `jose` and `@sphereon/oid4vci-client`
363
-
364
- ```typescript
365
- import { Jwt, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-client';
366
-
367
- const callbacks: ProofOfPossessionCallbacks = {
368
- signCallback,
369
- verifyCallback,
370
- };
371
-
372
- const keyPair = await jose.generateKeyPair('ES256');
373
- ```
374
-
375
- ### Using the builder from metadata and access token response
376
-
377
- Normally you would use the Proof of Possession builder using the server metadata and access token response together with
378
- the callbacks. There is however the possibility to use a JWT directly, which will be explained in the next section.
379
-
380
- ```typescript
381
- import { ProofOfPossessionBuilder } from '@sphereon/oid4vci-client';
382
-
383
- const proofInput: ProofOfPossession = await ProofOfPossessionBuilder.fromAccessTokenResponse({
384
- accessTokenResponse,
385
- callbacks,
386
- })
387
- .withEndpointMetadata(metadata)
388
- .withClientId('s6BhdRkqt3')
389
- .withKid('did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1')
390
- .build();
391
- console.log(proofInput);
392
- // {
393
- // "proof_type": "jwt",
394
- // "jwt": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMS9rZXlzLzEifQ.eyJpc3MiOiJzNkJoZFJrcXQzIiwiYXVkIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJpYXQiOjE2NTkxNDU5MjQsIm5vbmNlIjoidFppZ25zbkZicCJ9.btetOcsJ_VOePkwlFf2kyxm6hEUvPRimf3M-Dn3Lmzcmt5QiPToXNWxe_0fEJlRf4Ith55YGB43ScBe6ScZmD1gfLELYQF7LLg97yYlx_Iu8RLA2dS_7EWzLD3ZIzyUGf_uMq3HwXGJKL-ihroRpRBvxRLdZCy-j62nAzoTsBnlr6n79VjkGtlxIjN_CLGIQBhc3du3enghY6N4s3oXFrxWMl7UzGKdjCYN6vSagDb0MURjdiDCsK_yX4NyNd0nGpxqGhVgMpuhqEcqyU0qWPyHF-swtGG5JVAOJGd_YkJS5vbia8UdyOJXnAAdEE1E62a2yUPahNDxMh1iIpS0WO7y6QexWXdb5fmnWDst89T3ELS8Hj2Vzsw1XPyk9XR9JmiDzmEZdH05Wf4M9pXUG4-8_7StB6Lxc7_xDJdk6JPbzFgAIhJa4F_3rfPuwMseSEQvD6bDFowkIiUpt1vXGGVjVm3N4I4Th4_A2QpW4mDzcTKoZq9MKlDGXeLQBtiKXmqs10Jvzpp3O7kBwH7Qm6VUdBxk_-wsWplUZC4IvCfv23hy2SyFnh5zC6Wtw3UcbrSH6LcD7g-RNTKe4fRekyDxqLRdEm60BOozgBoTNhnetCrQ3e7HrApj9EP0vqNyXdtGGWCA011HVDnz6lVzf5yijJB8hOPpkgYGRmHdRQwI"
395
- // }
396
- ```
397
-
398
- ### Using the builder with a self-created JWT
399
-
400
- You can build/create a JWT yourself. You would still use the callbacks to sign the JWT. Please be aware that you will
401
- have to use the `c_nonce` value from the Access Token response as `nonce` value!. You can provide another nonce using
402
- the `jti` property.
403
-
404
- ```typescript
405
- import { Jwt, ProofOfPossessionBuilder, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-client';
406
-
407
- const callbacks: ProofOfPossessionCallbacks = {
408
- signCallback,
409
- verifyCallback,
410
- };
411
-
412
- const keyPair = await jose.generateKeyPair('ES256');
413
-
414
- // If you directly want to use a JWT, instead of using method on the ProofOfPossessionBuilder you can create JWT:
415
- const jwt: Jwt = {
416
- header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#1', typ: Typ.JWT },
417
- payload: { iss: 's6BhdRkqt3', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: 'https://issuer.example.com' },
418
- };
419
-
420
- const proofInput: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
421
- jwt,
422
- callbacks,
423
- }).build();
424
- console.log(proofInput);
425
- // {
426
- // "proof_type": "jwt",
427
- // "jwt": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMS9rZXlzLzEifQ.eyJpc3MiOiJzNkJoZFJrcXQzIiwiYXVkIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJpYXQiOjE2NTkxNDU5MjQsIm5vbmNlIjoidFppZ25zbkZicCJ9.btetOcsJ_VOePkwlFf2kyxm6hEUvPRimf3M-Dn3Lmzcmt5QiPToXNWxe_0fEJlRf4Ith55YGB43ScBe6ScZmD1gfLELYQF7LLg97yYlx_Iu8RLA2dS_7EWzLD3ZIzyUGf_uMq3HwXGJKL-ihroRpRBvxRLdZCy-j62nAzoTsBnlr6n79VjkGtlxIjN_CLGIQBhc3du3enghY6N4s3oXFrxWMl7UzGKdjCYN6vSagDb0MURjdiDCsK_yX4NyNd0nGpxqGhVgMpuhqEcqyU0qWPyHF-swtGG5JVAOJGd_YkJS5vbia8UdyOJXnAAdEE1E62a2yUPahNDxMh1iIpS0WO7y6QexWXdb5fmnWDst89T3ELS8Hj2Vzsw1XPyk9XR9JmiDzmEZdH05Wf4M9pXUG4-8_7StB6Lxc7_xDJdk6JPbzFgAIhJa4F_3rfPuwMseSEQvD6bDFowkIiUpt1vXGGVjVm3N4I4Th4_A2QpW4mDzcTKoZq9MKlDGXeLQBtiKXmqs10Jvzpp3O7kBwH7Qm6VUdBxk_-wsWplUZC4IvCfv23hy2SyFnh5zC6Wtw3UcbrSH6LcD7g-RNTKe4fRekyDxqLRdEm60BOozgBoTNhnetCrQ3e7HrApj9EP0vqNyXdtGGWCA011HVDnz6lVzf5yijJB8hOPpkgYGRmHdRQwI"
428
- // }
429
- ```
430
-
431
- ## Credential Issuance
432
-
433
- Now it is time to request the actual Credential(s) from the Issuer. The example uses a DID:JWK. The DID:JWK should match
434
- the keypair created earlier.
435
-
436
- ```typescript
437
- import { CredentialRequestClientBuilder, CredentialResponse, ProofOfPossessionArgs } from '@sphereon/oid4vci-client';
438
-
439
- const credentialRequestClient = CredentialRequestClientBuilder.fromCredentialOfferRequest(initiationRequestWithUrl, metadata).build();
440
-
441
- // In 1 step:
442
- const credentialResponse: CredentialResponse = await credentialRequestClient.acquireCredentialsUsingProof({
443
- proofInput,
444
- credentialType: 'OpenBadgeCredential', // Needs to match a type from the Initiate Issance Request!
445
- format: 'jwt_vc', // Allows us to override the format
446
- });
447
-
448
- // Or in 2 steps:
449
- // const credentialRequest: CredentialRequest = await credentialRequestClient.createCredentialRequest(proofOpts, { format: 'jwt_vc' }) // Allows us to override the format
450
- // const credentialResponse: CredentialResponse = await credentialRequestClient.acquireCredentialsUsingRequest(credentialRequest)
451
- ```
452
-
453
- # Helper Functions
454
-
455
- Several utility functions are available
456
-
457
- ## convertJsonToURI:
458
-
459
- Converts a Json object or string into an URI:
460
-
461
- ```typescript
462
- import { convertJsonToURI } from '@sphereon/oid4vci-client';
463
-
464
- const encodedURI = convertJsonToURI(
465
- {
466
- issuer: 'https://server.example.com',
467
- credential_type: ['https://did.example.org/healthCard', 'https://did.example1.org/driverLicense'],
468
- op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
469
- },
470
- {
471
- arrayTypeProperties: ['credential_type'],
472
- urlTypeProperties: ['issuer', 'credential_type'],
473
- },
474
- );
475
- console.log(encodedURI);
476
- // issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FdriverLicense&op_state=eyJhbGciOiJSU0Et...FYUaBy
477
- ```
478
-
479
- ## convertURIToJsonObject:
480
-
481
- Converts a URI into a Json object with URL decoded properties. Allows to provide which potential duplicate keys need to
482
- be converted into an array.
483
-
484
- ```typescript
485
- import { convertURIToJsonObject } from '@sphereon/oid4vci-client';
486
-
487
- const decodedJson = convertURIToJsonObject(
488
- 'issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FdriverLicense&op_state=eyJhbGciOiJSU0Et...FYUaBy',
489
- {
490
- arrayTypeProperties: ['credential_type'],
491
- requiredProperties: ['issuer', 'credential_type'],
492
- },
493
- );
494
- console.log(decodedJson);
495
- // {
496
- // issuer: 'https://server.example.com',
497
- // credential_type: ['https://did.example.org/healthCard', 'https://did.example1.org/driverLicense'],
498
- // op_state: 'eyJhbGciOiJSU0Et...FYUaBy'
499
- // }
500
- ```
501
-
502
- ## determineSpecVersionFromURI(uri: string): OpenId4VCIVersion
503
-
504
- ```typescript
505
- const CREDENTIAL_OFFER_URI =
506
- 'openid-credential-offer://?' +
507
- 'credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com%22,%22credentials%22:%5B%7B%22format%22:%22jwt_vc_json%22,%22types%22:%5B%22VerifiableCredential%22,%22UniversityDegreeCredential%22%5D%7D%5D,%22issuer_state%22:%22eyJhbGciOiJSU0Et...FYUaBy%22%7D';
508
-
509
- const openId4VCIVersion = determineSpecVersionFromURI(CREDENTIAL_OFFER_URI);
510
- console.log(openId4VCIVersion);
511
-
512
- /**
513
- * 11
514
- */
515
- ```
1
+ <h1 align="center">
2
+ <br>
3
+ <a href="https://www.sphereon.com"><img src="https://sphereon.com/content/themes/sphereon/assets/img/logo.svg" alt="Sphereon" width="400"></a>
4
+ <br>OpenID for Verifiable Credential Issuance - Client
5
+ <br>
6
+ </h1>
7
+
8
+ [![CI](https://github.com/Sphereon-Opensource/openid4vci-client/actions/workflows/main.yml/badge.svg)](https://github.com/Sphereon-Opensource/openid4vci-client/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/Sphereon-Opensource/openid4vci-client/branch/develop/graph/badge.svg)](https://codecov.io/gh/Sphereon-Opensource/openid4vci-client) [![NPM Version](https://img.shields.io/npm/v/@sphereon/oid4vci-client.svg)](https://npm.im/@sphereon/oid4vci-client)
9
+
10
+ _IMPORTANT this package is in an early development stage and currently only supports the pre-authorized code flow of
11
+ OpenID4VCI!_
12
+
13
+ # Background
14
+
15
+ A client to request and receive Verifiable Credentials using
16
+ the [OpenID for Verifiable Credential Issuance](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) (
17
+ OpenID4VCI) specification for receiving Verifiable Credentials as a holder/subject.
18
+
19
+ OpenID4VCI defines an API designated as Credential Endpoint that is used to issue verifiable credentials and
20
+ corresponding OAuth 2.0 based authorization mechanisms (see [RFC6749]) that a Wallet uses to obtain authorization to
21
+ receive verifiable credentials. W3C formats as well as other Credential formats are supported. This allows existing
22
+ OAuth 2.0 deployments and OpenID Connect OPs (see [OpenID.Core]) to extend their service and become Credential Issuers.
23
+ It also allows new applications built using Verifiable Credentials to utilize OAuth 2.0 as integration and
24
+ interoperability layer. This package provides holder/wallet support to interact with OpenID4VCI capable Issuer systems.
25
+
26
+ # Flows
27
+
28
+ The spec lists 2 flows. Currently only one is supported!
29
+
30
+ ## Authorized Code Flow
31
+
32
+ This flow isn't supported yet!
33
+
34
+ ## Pre-authorized Code Flow
35
+
36
+ The pre-authorized code flow assumes the user is using an out-of-band mechanism outside the issuance flow to
37
+ authenticate first.
38
+
39
+ The below diagram shows the steps involved in the pre-authorized code flow. Note that wallet inner functionalities (like
40
+ saving VCs) are out of scope for this library.
41
+
42
+ ![Flow diagram](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/Sphereon-Opensource/OID4VCI-client/develop/docs/preauthorized-code-flow.puml)
43
+
44
+ # OpenID4VCI Client
45
+
46
+ The OpenID4VCI client is the main client you typically will want to use. It combines several lower level classes into a
47
+ client you can use to finish the pre-authorized code flows.
48
+
49
+ ## Initiating the client
50
+
51
+ This initiates the client using a URI obtained from the Issuer using a link (URL) or QR code typically. We are also
52
+ already fetching the Server Metadata
53
+
54
+
55
+ Using openid-initiate-issuance scheme
56
+ ```typescript
57
+ import { OpenID4VCIClient } from '@sphereon/oid4vci-client';
58
+
59
+ // The client is initiated from a URI. This URI is provided by the Issuer, typically as a URL or QR code.
60
+ const client = await OpenID4VCIClient.fromURI({
61
+ uri: 'openid-initiate-issuance://?issuer=https%3A%2F%2Fissuer.research.identiproof.io&credential_type=OpenBadgeCredentialUrl&pre-authorized_code=4jLs9xZHEfqcoow0kHE7d1a8hUk6Sy-5bVSV2MqBUGUgiFFQi-ImL62T-FmLIo8hKA1UdMPH0lM1xAgcFkJfxIw9L-lI3mVs0hRT8YVwsEM1ma6N3wzuCdwtMU4bcwKp&user_pin_required=true',
62
+ kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#key-1', // Our DID. You can defer this also to when the acquireCredential method is called
63
+ alg: Alg.ES256, // The signing Algorithm we will use. You can defer this also to when the acquireCredential method is called
64
+ clientId: 'test-clientId', // The clientId if the Authrozation Service requires it. If a clientId is needed you can defer this also to when the acquireAccessToken method is called
65
+ retrieveServerMetadata: true, // Already retrieve the server metadata. Can also be done afterwards by invoking a method yourself.
66
+ });
67
+
68
+ console.log(client.getIssuer()); // https://issuer.research.identiproof.io
69
+ console.log(client.getCredentialEndpoint()); // https://issuer.research.identiproof.io/credential
70
+ console.log(client.getAccessTokenEndpoint()); // https://auth.research.identiproof.io/oauth2/token
71
+ ```
72
+
73
+ Using https scheme
74
+ ```typescript
75
+ import { OpenID4VCIClient } from '@sphereon/oid4vci-client';
76
+
77
+ // The client is initiated from a URI. This URI is provided by the Issuer, typically as a URL or QR code.
78
+ const client = await OpenID4VCIClient.fromURI({
79
+ uri: 'https://launchpad.vii.electron.mattrlabs.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Flaunchpad.vii.electron.mattrlabs.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22types%22%3A%5B%22OpenBadgeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X%22%7D%7D%7D',
80
+ kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#key-1', // Our DID. You can defer this also to when the acquireCredential method is called
81
+ alg: Alg.ES256, // The signing Algorithm we will use. You can defer this also to when the acquireCredential method is called
82
+ clientId: 'test-clientId', // The clientId if the Authrozation Service requires it. If a clientId is needed you can defer this also to when the acquireAccessToken method is called
83
+ retrieveServerMetadata: true, // Already retrieve the server metadata. Can also be done afterwards by invoking a method yourself.
84
+ });
85
+
86
+ console.log(client.getIssuer()); // https://launchpad.vii.electron.mattrlabs.io
87
+ console.log(client.getCredentialEndpoint()); // https://launchpad.vii.electron.mattrlabs.io/credential
88
+ console.log(client.getAccessTokenEndpoint()); // https://launchpad.vii.electron.mattrlabs.io/oauth2/token
89
+ ```
90
+
91
+ ## Server metadata
92
+
93
+ The OID4VCI Server metadata contains information about token endpoints, credential endpoints, as well as additional
94
+ information about supported Credentials, and their cryptographic suites and formats.
95
+ The code above already retrieved the metadata, so it will not be fetched again, and this method places the data in another variable. If you however have not used
96
+ the `retrieveServerMetadata` option, you can use this method to fetch it from the Issuer:
97
+
98
+ ```typescript
99
+ const metadata = await client.retrieveServerMetadata();
100
+ ```
101
+
102
+ ## Access token from Authorization Server
103
+
104
+ Next we need to get an Access token from the OAuth2 Authorization Server using the token endpoint. This endpoint is
105
+ found from the metadata if the server supports it. Otherwise a default location based on the issuer value from the
106
+ Initiate Issuance Request is used.
107
+
108
+ ```typescript
109
+ const accessToken = await client.acquireAccessToken({ pin: '1234' });
110
+ console.log(accessToken);
111
+ /**
112
+ * {
113
+ * access_token: 'ey6546.546654.64565',
114
+ * authorization_pending: false,
115
+ * c_nonce: 'c_nonce2022101300',
116
+ * c_nonce_expires_in: 2025101300,
117
+ * interval: 2025101300,
118
+ * token_type: 'Bearer',
119
+ * }
120
+ */
121
+ ```
122
+
123
+ ## Getting the credential
124
+
125
+ Now it is time to get the credential. In order to achieve this, we will be using the metadata together with the access
126
+ token, but first we will have to create a so-called Proof of Possession. Please see
127
+ the [Proof of Posession](#proof-of-possession) chapter for more information.
128
+
129
+ The Proof of Possession using a signature callback function. The example uses the `jose` library.
130
+
131
+ ```typescript
132
+ import * as jose from 'jose';
133
+ import { DIDDocument } from 'did-resolver';
134
+
135
+ const { privateKey, publicKey } = await jose.generateKeyPair('ES256');
136
+
137
+ // Must be JWS
138
+ async function signCallback(args: Jwt, kid: string): Promise<string> {
139
+ return await new jose.SignJWT({ ...args.payload })
140
+ .setProtectedHeader({ alg: args.header.alg })
141
+ .setIssuedAt()
142
+ .setIssuer(kid)
143
+ .setAudience(args.payload.aud)
144
+ .setExpirationTime('2h')
145
+ .sign(privateKey);
146
+ }
147
+
148
+ const callbacks: ProofOfPossessionCallbacks<DIDDocument> = {
149
+ signCallback,
150
+ };
151
+ ```
152
+
153
+ Now it is time to get the actual credential
154
+
155
+ ```typescript
156
+ const credentialResponse = await client.acquireCredentials({
157
+ credentialTypes: 'OpenBadgeCredential',
158
+ proofCallbacks: callbacks,
159
+ format: 'jwt_vc_json',
160
+ alg: Alg.ES256K,
161
+ kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1',
162
+ });
163
+ console.log(credentialResponse.credential);
164
+ // JWT format. (LDP / JSON-LD ('ldp_vc' / 'jwt_vc_json-ld') is also supported by the client)
165
+ // eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly9leGFtcGxlLmVkdS9pc3N1ZXJzLzU2NTA0OSIsImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMDA6MDA6MDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.z5vgMTK1nfizNCg5N-niCOL3WUIAL7nXy-nGhDZYO_-PNGeE-0djCpWAMH8fD8eWSID5PfkPBYkx_dfLJnQ7NA
166
+ ```
167
+
168
+ # Using individual classes and methods instead of the client
169
+
170
+ Instead of using the OpenID4VCI Client, you can also use the separate classes if you want. This typically gives you a
171
+ bit more control and options, at the expense of a bit more complexity.
172
+
173
+ ## Issuance Initiation
174
+
175
+ Issuance is started from a so-called Issuance Initiation Request by the Issuer. This typically is URI, exposed
176
+ as a link or a QR code. You can call the `CredentialOffer.fromURI(uri)` method to parse the URI into a Json object
177
+ containing the baseUrl and a `uri` JSON object
178
+
179
+ ```typescript
180
+ import { CredentialOffer } from '@sphereon/oid4vci-client';
181
+
182
+ const initiationURI =
183
+ 'https://issuer.example.com?issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FdriverLicense&op_state=eyJhbGciOiJSU0Et...FYUaBy';
184
+
185
+ const initiationRequestWithUrl = CredentialOffer.fromURI(initiationURI);
186
+ console.log(initiationRequestWithUrl);
187
+
188
+ /**
189
+ * {
190
+ * "baseUrl": "https://server.example.com",
191
+ * "request": {
192
+ * "credential_type": [
193
+ * "https://did.example.org/healthCard",
194
+ * "https://did.example.org/driverLicense"
195
+ * ],
196
+ * "issuer": "https://server.example.com",
197
+ * "op_state": "eyJhbGciOiJSU0Et...FYUaBy"
198
+ * },
199
+ * "version": 9
200
+ * }
201
+ */
202
+ ```
203
+
204
+ ## Getting OpenID4VCI Server and OIDC/OAuth2 metadata
205
+
206
+ The OpenID4VCI spec defines a server metadata object that contains information about the issuer and the credentials they
207
+ support. Next to this predefined endpoint there are also the well-known locations for OpenID Connect Discovery
208
+ configuration and
209
+ Oauth2 Authorization Server configuration. These contain for instance the token endpoints.
210
+ The MetadataClient checks the OpenID4VCI well-known location for the medata and existence of a token endpoint. If the
211
+ OpenID4VCI well-known location is not found, the OIDC/OAuth2 well-known locations will be tried:
212
+
213
+ Example:
214
+
215
+ ```typescript
216
+ import { MetadataClient } from '@sphereon/oid4vci-client';
217
+
218
+ const metadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);
219
+
220
+ console.log(metadata);
221
+ /**
222
+ * {
223
+ * issuer: 'https://server.example.com',
224
+ * credential_endpoint: 'https://server.example.com/credential',
225
+ * token_endpoint: 'https://server.example.com/token',
226
+ * jwks_uri: 'https://server.example.com/jwks',
227
+ * grant_types_supported: ['urn:ietf:params:oauth:grant-type:pre-authorized_code'],
228
+ * credentials_supported: {
229
+ * OpenBadgeCredential: {
230
+ * formats: {
231
+ * jwt_vc: {
232
+ * types: [
233
+ * 'https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential',
234
+ * 'https://w3id.org/ngi/OpenBadgeExtendedCredential',
235
+ * ],
236
+ * binding_methods_supported: ['did'],
237
+ * cryptographic_suites_supported: ['ES256'],
238
+ * },
239
+ * },
240
+ * },
241
+ * },
242
+ * }
243
+ */
244
+ ```
245
+
246
+ ## Acquiring the Access Token
247
+
248
+ Now you will need to get an access token from the oAuth2 Authorization Server (AS), using some values from
249
+ the `IssuanceInitiationRequestPayloadV9` payload.
250
+ For now, you can use the issuer hostname for the AS, as there is no way to know the AS from the Issuance Initiation for
251
+ known until the
252
+ following [OpenID Ticket](https://bitbucket.org/openid/connect/issues/1632/issuer-metadata-clarification-needed) is
253
+ resolved. So the token endpoint would become https://<issuer-hostname>/token.
254
+ The library allows to pass in a different value for the AS token endpoint as well, so you already can use a different AS
255
+ if you know the AS upfront. If no AS is provided the issuer value from the Issuance Initiation Request will be used.
256
+
257
+ ```typescript
258
+ import { AccessTokenClient, AuthorizationServerOpts } from '@sphereon/oid4vci-client';
259
+
260
+ const clientId = 'abcd'; // This can be a random value or a clientId assigned by the Authorization Server (depends on the environment)
261
+ const pin = 1234; // A pincode which is shown out of band typically. Only use when the pin-code is required from the Issuance Initiation object.
262
+
263
+ // Allows to override the Authorization Server and provide other AS options. By default the issuer value will be used
264
+ const asOpts: AuthorizationServerOpts = {
265
+ clientId,
266
+ };
267
+
268
+ const accessTokenResponse = AccessTokenClient.acquireAccessTokenUsingRequest({
269
+ credentialOffer,
270
+ asOpts,
271
+ pin,
272
+ metadata,
273
+ });
274
+ console.log(accessTokenResponse);
275
+ /**
276
+ * {
277
+ * access_token: "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ"
278
+ * token_type: "bearer",
279
+ * expires_in: 86400
280
+ * }
281
+ */
282
+ ```
283
+
284
+ # Proof of Possession
285
+
286
+ Part of OpenID4VCI is the holder showing that they are in possession of a certain key, associated with the DID that will
287
+ be the subject of the to be issued Verifiable Credential.
288
+ This proof of possession will be created using a DID, it's associated keypair and the `ProofOfPossessionBuilder` class.
289
+ This Builder can be initiated from a JWT object if you want to create a JWT yourself, or it can be build using the
290
+ Initiate Issuance Request, Server metadata and some methods from the builder. Both approaches need a callback function
291
+ to sign the JWT and optionally a callback to verify the JWT.
292
+ The signature of the callback functions you need to implement are:
293
+
294
+ ```typescript
295
+ export type JWTSignerCallback = (jwt: Jwt, kid: string) => Promise<string>;
296
+ export type JWTVerifyCallback = (args: { jwt: string; kid: string }) => Promise<void>;
297
+ ```
298
+
299
+ This is an example of the signature callback function created using the `jose` library.
300
+
301
+ ```typescript
302
+ import { Jwt } from '@sphereon/oid4vci-client';
303
+
304
+ const { privateKey, publicKey } = await jose.generateKeyPair('ES256');
305
+
306
+ // Must be JWS
307
+ async function signCallback(args: Jwt, kid: string): Promise<string> {
308
+ return await new jose.SignJWT({ ...args.payload })
309
+ .setProtectedHeader({ alg: args.header.alg })
310
+ .setIssuedAt()
311
+ .setIssuer(kid)
312
+ .setAudience(args.payload.aud)
313
+ .setExpirationTime('2h')
314
+ .sign(keypair.privateKey);
315
+ }
316
+ ```
317
+
318
+ Alongside signing, you can optionally provide another callback function for verifying the created signature with
319
+ populating `verifyCallback`. The method is expected to throw errors in case problems with the JWT or it's signature are
320
+ found.
321
+ below is an example of such method. This example (like the previous one) uses `jose` to verify the jwt.
322
+
323
+ ```typescript
324
+ async function verifyCallback(args: { jwt: string; kid: string }): Promise<void> {
325
+ await jose.compactVerify(args.jwt, keypair.publicKey);
326
+ }
327
+ ```
328
+
329
+ Some important interface around Proof of Possession:
330
+
331
+ ```typescript
332
+ export enum Alg {
333
+ EdDSA = 'EdDSA',
334
+ ES256 = 'ES256',
335
+ ES256K = 'ES256K',
336
+ }
337
+
338
+ export interface JWTHeader {
339
+ alg: Alg; // REQUIRED by the JWT signer
340
+ typ?: string; //JWT always
341
+ kid?: string; // CONDITIONAL. JWT header containing the key ID. If the Credential shall be bound to a DID, the kid refers to a DID URL which identifies a particular key in the DID Document that the Credential shall be bound to. MUST NOT be present if jwk or x5c is present.
342
+ jwk?: JWK; // CONDITIONAL. JWT header containing the key material the new Credential shall be bound to. MUST NOT be present if kid or x5c is present.
343
+ x5c?: string[]; // CONDITIONAL. JWT header containing a certificate or certificate chain corresponding to the key used to sign the JWT. This element may be used to convey a key attestation. In such a case, the actual key certificate will contain attributes related to the key properties. MUST NOT be present if kid or jwk is present.
344
+ }
345
+
346
+ export interface JWTPayload {
347
+ iss?: string; // REQUIRED (string). The value of this claim MUST be the client_id of the client making the credential request.
348
+ aud?: string; // REQUIRED (string). The value of this claim MUST be the issuer URL of credential issuer.
349
+ iat?: number; // REQUIRED (number). The value of this claim MUST be the time at which the proof was issued using the syntax defined in [RFC7519].
350
+ nonce?: string; // REQUIRED (string). The value type of this claim MUST be a string, where the value is a c_nonce provided by the credential issuer. //TODO: Marked as required not present in NGI flow
351
+ jti?: string; // A new nonce chosen by the wallet. Used to prevent replay
352
+ exp?: number; // Not longer than 5 minutes
353
+ }
354
+
355
+ export interface Jwt {
356
+ header?: JWTHeader;
357
+ payload?: JWTPayload;
358
+ }
359
+ ```
360
+
361
+ The arguments requested by `jose` and `@sphereon/oid4vci-client`
362
+
363
+ ```typescript
364
+ import { Jwt, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-client';
365
+
366
+ const callbacks: ProofOfPossessionCallbacks = {
367
+ signCallback,
368
+ verifyCallback,
369
+ };
370
+
371
+ const keyPair = await jose.generateKeyPair('ES256');
372
+ ```
373
+
374
+ ### Using the builder from metadata and access token response
375
+
376
+ Normally you would use the Proof of Possession builder using the server metadata and access token response together with
377
+ the callbacks. There is however the possibility to use a JWT directly, which will be explained in the next section.
378
+
379
+ ```typescript
380
+ import { ProofOfPossessionBuilder } from '@sphereon/oid4vci-client';
381
+
382
+ const proofInput: ProofOfPossession = await ProofOfPossessionBuilder.fromAccessTokenResponse({
383
+ accessTokenResponse,
384
+ callbacks,
385
+ })
386
+ .withEndpointMetadata(metadata)
387
+ .withClientId('s6BhdRkqt3')
388
+ .withKid('did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1')
389
+ .build();
390
+ console.log(proofInput);
391
+ // {
392
+ // "proof_type": "jwt",
393
+ // "jwt": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMS9rZXlzLzEifQ.eyJpc3MiOiJzNkJoZFJrcXQzIiwiYXVkIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJpYXQiOjE2NTkxNDU5MjQsIm5vbmNlIjoidFppZ25zbkZicCJ9.btetOcsJ_VOePkwlFf2kyxm6hEUvPRimf3M-Dn3Lmzcmt5QiPToXNWxe_0fEJlRf4Ith55YGB43ScBe6ScZmD1gfLELYQF7LLg97yYlx_Iu8RLA2dS_7EWzLD3ZIzyUGf_uMq3HwXGJKL-ihroRpRBvxRLdZCy-j62nAzoTsBnlr6n79VjkGtlxIjN_CLGIQBhc3du3enghY6N4s3oXFrxWMl7UzGKdjCYN6vSagDb0MURjdiDCsK_yX4NyNd0nGpxqGhVgMpuhqEcqyU0qWPyHF-swtGG5JVAOJGd_YkJS5vbia8UdyOJXnAAdEE1E62a2yUPahNDxMh1iIpS0WO7y6QexWXdb5fmnWDst89T3ELS8Hj2Vzsw1XPyk9XR9JmiDzmEZdH05Wf4M9pXUG4-8_7StB6Lxc7_xDJdk6JPbzFgAIhJa4F_3rfPuwMseSEQvD6bDFowkIiUpt1vXGGVjVm3N4I4Th4_A2QpW4mDzcTKoZq9MKlDGXeLQBtiKXmqs10Jvzpp3O7kBwH7Qm6VUdBxk_-wsWplUZC4IvCfv23hy2SyFnh5zC6Wtw3UcbrSH6LcD7g-RNTKe4fRekyDxqLRdEm60BOozgBoTNhnetCrQ3e7HrApj9EP0vqNyXdtGGWCA011HVDnz6lVzf5yijJB8hOPpkgYGRmHdRQwI"
394
+ // }
395
+ ```
396
+
397
+ ### Using the builder with a self-created JWT
398
+
399
+ You can build/create a JWT yourself. You would still use the callbacks to sign the JWT. Please be aware that you will
400
+ have to use the `c_nonce` value from the Access Token response as `nonce` value!. You can provide another nonce using
401
+ the `jti` property.
402
+
403
+ ```typescript
404
+ import { Jwt, ProofOfPossessionBuilder, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-client';
405
+
406
+ const callbacks: ProofOfPossessionCallbacks = {
407
+ signCallback,
408
+ verifyCallback,
409
+ };
410
+
411
+ const keyPair = await jose.generateKeyPair('ES256');
412
+
413
+ // If you directly want to use a JWT, instead of using method on the ProofOfPossessionBuilder you can create JWT:
414
+ const jwt: Jwt = {
415
+ header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#1', typ: Typ.JWT },
416
+ payload: { iss: 's6BhdRkqt3', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: 'https://issuer.example.com' },
417
+ };
418
+
419
+ const proofInput: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
420
+ jwt,
421
+ callbacks,
422
+ }).build();
423
+ console.log(proofInput);
424
+ // {
425
+ // "proof_type": "jwt",
426
+ // "jwt": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMS9rZXlzLzEifQ.eyJpc3MiOiJzNkJoZFJrcXQzIiwiYXVkIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJpYXQiOjE2NTkxNDU5MjQsIm5vbmNlIjoidFppZ25zbkZicCJ9.btetOcsJ_VOePkwlFf2kyxm6hEUvPRimf3M-Dn3Lmzcmt5QiPToXNWxe_0fEJlRf4Ith55YGB43ScBe6ScZmD1gfLELYQF7LLg97yYlx_Iu8RLA2dS_7EWzLD3ZIzyUGf_uMq3HwXGJKL-ihroRpRBvxRLdZCy-j62nAzoTsBnlr6n79VjkGtlxIjN_CLGIQBhc3du3enghY6N4s3oXFrxWMl7UzGKdjCYN6vSagDb0MURjdiDCsK_yX4NyNd0nGpxqGhVgMpuhqEcqyU0qWPyHF-swtGG5JVAOJGd_YkJS5vbia8UdyOJXnAAdEE1E62a2yUPahNDxMh1iIpS0WO7y6QexWXdb5fmnWDst89T3ELS8Hj2Vzsw1XPyk9XR9JmiDzmEZdH05Wf4M9pXUG4-8_7StB6Lxc7_xDJdk6JPbzFgAIhJa4F_3rfPuwMseSEQvD6bDFowkIiUpt1vXGGVjVm3N4I4Th4_A2QpW4mDzcTKoZq9MKlDGXeLQBtiKXmqs10Jvzpp3O7kBwH7Qm6VUdBxk_-wsWplUZC4IvCfv23hy2SyFnh5zC6Wtw3UcbrSH6LcD7g-RNTKe4fRekyDxqLRdEm60BOozgBoTNhnetCrQ3e7HrApj9EP0vqNyXdtGGWCA011HVDnz6lVzf5yijJB8hOPpkgYGRmHdRQwI"
427
+ // }
428
+ ```
429
+
430
+ ## Credential Issuance
431
+
432
+ Now it is time to request the actual Credential(s) from the Issuer. The example uses a DID:JWK. The DID:JWK should match
433
+ the keypair created earlier.
434
+
435
+ ```typescript
436
+ import { CredentialRequestClientBuilder, CredentialResponse, ProofOfPossessionArgs } from '@sphereon/oid4vci-client';
437
+
438
+ const credentialRequestClient = CredentialRequestClientBuilder.fromCredentialOfferRequest(initiationRequestWithUrl, metadata).build();
439
+
440
+ // In 1 step:
441
+ const credentialResponse: CredentialResponse = await credentialRequestClient.acquireCredentialsUsingProof({
442
+ proofInput,
443
+ credentialType: 'OpenBadgeCredential', // Needs to match a type from the Initiate Issance Request!
444
+ format: 'jwt_vc', // Allows us to override the format
445
+ });
446
+
447
+ // Or in 2 steps:
448
+ // const credentialRequest: CredentialRequest = await credentialRequestClient.createCredentialRequest(proofOpts, { format: 'jwt_vc' }) // Allows us to override the format
449
+ // const credentialResponse: CredentialResponse = await credentialRequestClient.acquireCredentialsUsingRequest(credentialRequest)
450
+ ```
451
+
452
+ # Helper Functions
453
+
454
+ Several utility functions are available
455
+
456
+ ## convertJsonToURI:
457
+
458
+ Converts a Json object or string into an URI:
459
+
460
+ ```typescript
461
+ import { convertJsonToURI } from '@sphereon/oid4vci-client';
462
+
463
+ const encodedURI = convertJsonToURI(
464
+ {
465
+ issuer: 'https://server.example.com',
466
+ credential_type: ['https://did.example.org/healthCard', 'https://did.example1.org/driverLicense'],
467
+ op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
468
+ },
469
+ {
470
+ arrayTypeProperties: ['credential_type'],
471
+ urlTypeProperties: ['issuer', 'credential_type'],
472
+ },
473
+ );
474
+ console.log(encodedURI);
475
+ // issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FdriverLicense&op_state=eyJhbGciOiJSU0Et...FYUaBy
476
+ ```
477
+
478
+ ## convertURIToJsonObject:
479
+
480
+ Converts a URI into a Json object with URL decoded properties. Allows to provide which potential duplicate keys need to
481
+ be converted into an array.
482
+
483
+ ```typescript
484
+ import { convertURIToJsonObject } from '@sphereon/oid4vci-client';
485
+
486
+ const decodedJson = convertURIToJsonObject(
487
+ 'issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FdriverLicense&op_state=eyJhbGciOiJSU0Et...FYUaBy',
488
+ {
489
+ arrayTypeProperties: ['credential_type'],
490
+ requiredProperties: ['issuer', 'credential_type'],
491
+ },
492
+ );
493
+ console.log(decodedJson);
494
+ // {
495
+ // issuer: 'https://server.example.com',
496
+ // credential_type: ['https://did.example.org/healthCard', 'https://did.example1.org/driverLicense'],
497
+ // op_state: 'eyJhbGciOiJSU0Et...FYUaBy'
498
+ // }
499
+ ```
500
+
501
+ ## determineSpecVersionFromURI(uri: string): OpenId4VCIVersion
502
+
503
+ ```typescript
504
+ const CREDENTIAL_OFFER_URI =
505
+ 'openid-credential-offer://?' +
506
+ 'credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com%22,%22credentials%22:%5B%7B%22format%22:%22jwt_vc_json%22,%22types%22:%5B%22VerifiableCredential%22,%22UniversityDegreeCredential%22%5D%7D%5D,%22issuer_state%22:%22eyJhbGciOiJSU0Et...FYUaBy%22%7D';
507
+
508
+ const openId4VCIVersion = determineSpecVersionFromURI(CREDENTIAL_OFFER_URI);
509
+ console.log(openId4VCIVersion);
510
+
511
+ /**
512
+ * 11
513
+ */
514
+ ```