diff --git a/packages/callback-example/lib/__tests__/issuerCallback.spec.ts b/packages/callback-example/lib/__tests__/issuerCallback.spec.ts index abebc5fa..e50139e1 100644 --- a/packages/callback-example/lib/__tests__/issuerCallback.spec.ts +++ b/packages/callback-example/lib/__tests__/issuerCallback.spec.ts @@ -1,6 +1,11 @@ import { KeyObject } from 'crypto' -import { CredentialRequestClient, CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '@sphereon/oid4vci-client' +import { + CredentialRequestClient, + CredentialRequestClientBuilder, + CredentialRequestClientBuilderV1_0_13, + ProofOfPossessionBuilder, +} from '@sphereon/oid4vci-client' import { Alg, CNonceState, @@ -219,7 +224,7 @@ describe('issuerCallback', () => { }) it('Should pass requesting a verifiable credential using the client', async () => { - const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI })) + const credReqClient = ((await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI })) as CredentialRequestClientBuilderV1_0_13) .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') .withCredentialEndpointFromMetadata({ credential_configurations_supported: { VeriCred: { format: 'jwt_vc_json' } }, diff --git a/packages/client/lib/AuthorizationCodeClient.ts b/packages/client/lib/AuthorizationCodeClient.ts index 81d3f615..6de5e665 100644 --- a/packages/client/lib/AuthorizationCodeClient.ts +++ b/packages/client/lib/AuthorizationCodeClient.ts @@ -35,23 +35,26 @@ export async function createSignedAuthRequestWhenNeeded(requestObject: Record= OpenId4VCIVersion.VER_1_0_13) { + return CredentialRequestClientBuilderV1_0_13.fromCredentialIssuer({ + credentialIssuer, + metadata, + version, + credentialIdentifier, + credentialTypes, + }); + } else { + if (!credentialTypes || credentialTypes.length === 0) { + throw new Error('CredentialTypes must be provided for v1_0_11'); + } + return CredentialRequestClientBuilderV1_0_11.fromCredentialIssuer({ credentialIssuer, metadata, version, credentialTypes }); } - return builder; } - public static async fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): Promise { + public static async fromURI({ + uri, + metadata, + }: { + uri: string; + metadata?: EndpointMetadata; + }): Promise { const offer = await CredentialOfferClient.fromURI(uri); return CredentialRequestClientBuilder.fromCredentialOfferRequest({ request: offer, ...offer, metadata, version: offer.version }); } @@ -68,25 +58,13 @@ export class CredentialRequestClientBuilder { baseUrl?: string; version?: OpenId4VCIVersion; metadata?: EndpointMetadata; - }): CredentialRequestClientBuilder { - const { request, metadata } = opts; + }): CredentialRequestClientBuilderV1_0_11 | CredentialRequestClientBuilderV1_0_13 { + const { request } = opts; const version = opts.version ?? request.version ?? determineSpecVersionFromOffer(request.original_credential_offer); if (version < OpenId4VCIVersion.VER_1_0_13) { - throw new Error('Versions below v1.0.13 (draft 13) are not supported.'); - } - const builder = new CredentialRequestClientBuilder(); - const issuer = getIssuerFromCredentialOfferPayload(request.credential_offer) ?? (metadata?.issuer as string); - builder.withVersion(version); - builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`)); - if (metadata?.deferred_credential_endpoint) { - builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint); - } - const ids: string[] = (request.credential_offer as CredentialOfferPayloadV1_0_13).credential_configuration_ids; - // if there's only one in the offer, we pre-select it. if not, you should provide the credentialType - if (ids.length && ids.length === 1) { - builder.withCredentialIdentifier(ids[0]); + return CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest(opts); } - return builder; + return CredentialRequestClientBuilderV1_0_13.fromCredentialOfferRequest(opts); } public static fromCredentialOffer({ @@ -95,79 +73,17 @@ export class CredentialRequestClientBuilder { }: { credentialOffer: CredentialOfferRequestWithBaseUrl; metadata?: EndpointMetadata; - }): CredentialRequestClientBuilder { - return CredentialRequestClientBuilder.fromCredentialOfferRequest({ - request: credentialOffer, + }): CredentialRequestClientBuilderV1_0_11 | CredentialRequestClientBuilderV1_0_13 { + const version = determineSpecVersionFromOffer(credentialOffer.credential_offer); + if (version < OpenId4VCIVersion.VER_1_0_13) { + return CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({ + credentialOffer, + metadata, + }); + } + return CredentialRequestClientBuilderV1_0_13.fromCredentialOffer({ + credentialOffer, metadata, - version: credentialOffer.version, }); } - - public withCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this { - this.credentialEndpoint = metadata.credential_endpoint; - return this; - } - - public withCredentialEndpoint(credentialEndpoint: string): this { - this.credentialEndpoint = credentialEndpoint; - return this; - } - - public withDeferredCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this { - this.deferredCredentialEndpoint = metadata.deferred_credential_endpoint; - return this; - } - - public withDeferredCredentialEndpoint(deferredCredentialEndpoint: string): this { - this.deferredCredentialEndpoint = deferredCredentialEndpoint; - return this; - } - - public withDeferredCredentialAwait(deferredCredentialAwait: boolean, deferredCredentialIntervalInMS?: number): this { - this.deferredCredentialAwait = deferredCredentialAwait; - this.deferredCredentialIntervalInMS = deferredCredentialIntervalInMS ?? 5000; - return this; - } - - public withCredentialIdentifier(credentialIdentifier: string): this { - this.credentialIdentifier = credentialIdentifier; - return this; - } - - public withCredentialType(credentialTypes: string | string[]): this { - this.credentialTypes = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes]; - return this; - } - - public withFormat(format: CredentialFormat | OID4VCICredentialFormat): this { - this.format = format; - return this; - } - - public withSubjectIssuance(subjectIssuance: ExperimentalSubjectIssuance): this { - this.subjectIssuance = subjectIssuance; - return this; - } - - public withToken(accessToken: string): this { - this.token = accessToken; - return this; - } - - public withTokenFromResponse(response: AccessTokenResponse): this { - this.token = response.access_token; - return this; - } - - public withVersion(version: OpenId4VCIVersion): this { - this.version = version; - return this; - } - - public build(): CredentialRequestClient { - if (!this.version) { - this.withVersion(OpenId4VCIVersion.VER_1_0_11); - } - return new CredentialRequestClient(this); - } } diff --git a/packages/client/lib/CredentialRequestClientBuilderV1_0_13.ts b/packages/client/lib/CredentialRequestClientBuilderV1_0_13.ts new file mode 100644 index 00000000..23508740 --- /dev/null +++ b/packages/client/lib/CredentialRequestClientBuilderV1_0_13.ts @@ -0,0 +1,173 @@ +import { + AccessTokenResponse, + CredentialIssuerMetadataV1_0_13, + CredentialOfferPayloadV1_0_13, + CredentialOfferRequestWithBaseUrl, + determineSpecVersionFromOffer, + EndpointMetadata, + ExperimentalSubjectIssuance, + getIssuerFromCredentialOfferPayload, + OID4VCICredentialFormat, + OpenId4VCIVersion, + UniformCredentialOfferRequest, +} from '@sphereon/oid4vci-common'; +import { CredentialFormat } from '@sphereon/ssi-types'; + +import { CredentialOfferClient } from './CredentialOfferClient'; +import { CredentialRequestClient } from './CredentialRequestClient'; + +export class CredentialRequestClientBuilderV1_0_13 { + credentialEndpoint?: string; + deferredCredentialEndpoint?: string; + deferredCredentialAwait = false; + deferredCredentialIntervalInMS = 5000; + credentialIdentifier?: string; + credentialTypes?: string[] = []; + format?: CredentialFormat | OID4VCICredentialFormat; + token?: string; + version?: OpenId4VCIVersion; + subjectIssuance?: ExperimentalSubjectIssuance; + + public static fromCredentialIssuer({ + credentialIssuer, + metadata, + version, + credentialIdentifier, + credentialTypes, + }: { + credentialIssuer: string; + metadata?: EndpointMetadata; + version?: OpenId4VCIVersion; + credentialIdentifier?: string; + credentialTypes?: string | string[]; + }): CredentialRequestClientBuilderV1_0_13 { + const issuer = credentialIssuer; + const builder = new CredentialRequestClientBuilderV1_0_13(); + builder.withVersion(version ?? OpenId4VCIVersion.VER_1_0_13); + builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`)); + if (metadata?.deferred_credential_endpoint) { + builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint); + } + if (credentialIdentifier) { + builder.withCredentialIdentifier(credentialIdentifier); + } + if (credentialTypes) { + builder.withCredentialType(credentialTypes); + } + return builder; + } + + public static async fromURI({ uri, metadata }: { uri: string; metadata?: EndpointMetadata }): Promise { + const offer = await CredentialOfferClient.fromURI(uri); + return CredentialRequestClientBuilderV1_0_13.fromCredentialOfferRequest({ request: offer, ...offer, metadata, version: offer.version }); + } + + public static fromCredentialOfferRequest(opts: { + request: UniformCredentialOfferRequest; + scheme?: string; + baseUrl?: string; + version?: OpenId4VCIVersion; + metadata?: EndpointMetadata; + }): CredentialRequestClientBuilderV1_0_13 { + const { request, metadata } = opts; + const version = opts.version ?? request.version ?? determineSpecVersionFromOffer(request.original_credential_offer); + if (version < OpenId4VCIVersion.VER_1_0_13) { + throw new Error('Versions below v1.0.13 (draft 13) are not supported.'); + } + const builder = new CredentialRequestClientBuilderV1_0_13(); + const issuer = getIssuerFromCredentialOfferPayload(request.credential_offer) ?? (metadata?.issuer as string); + builder.withVersion(version); + builder.withCredentialEndpoint(metadata?.credential_endpoint ?? (issuer.endsWith('/') ? `${issuer}credential` : `${issuer}/credential`)); + if (metadata?.deferred_credential_endpoint) { + builder.withDeferredCredentialEndpoint(metadata.deferred_credential_endpoint); + } + const ids: string[] = (request.credential_offer as CredentialOfferPayloadV1_0_13).credential_configuration_ids; + // if there's only one in the offer, we pre-select it. if not, you should provide the credentialType + if (ids.length && ids.length === 1) { + builder.withCredentialIdentifier(ids[0]); + } + return builder; + } + + public static fromCredentialOffer({ + credentialOffer, + metadata, + }: { + credentialOffer: CredentialOfferRequestWithBaseUrl; + metadata?: EndpointMetadata; + }): CredentialRequestClientBuilderV1_0_13 { + return CredentialRequestClientBuilderV1_0_13.fromCredentialOfferRequest({ + request: credentialOffer, + metadata, + version: credentialOffer.version, + }); + } + + public withCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this { + this.credentialEndpoint = metadata.credential_endpoint; + return this; + } + + public withCredentialEndpoint(credentialEndpoint: string): this { + this.credentialEndpoint = credentialEndpoint; + return this; + } + + public withDeferredCredentialEndpointFromMetadata(metadata: CredentialIssuerMetadataV1_0_13): this { + this.deferredCredentialEndpoint = metadata.deferred_credential_endpoint; + return this; + } + + public withDeferredCredentialEndpoint(deferredCredentialEndpoint: string): this { + this.deferredCredentialEndpoint = deferredCredentialEndpoint; + return this; + } + + public withDeferredCredentialAwait(deferredCredentialAwait: boolean, deferredCredentialIntervalInMS?: number): this { + this.deferredCredentialAwait = deferredCredentialAwait; + this.deferredCredentialIntervalInMS = deferredCredentialIntervalInMS ?? 5000; + return this; + } + + public withCredentialIdentifier(credentialIdentifier: string): this { + this.credentialIdentifier = credentialIdentifier; + return this; + } + + public withCredentialType(credentialTypes: string | string[]): this { + this.credentialTypes = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes]; + return this; + } + + public withFormat(format: CredentialFormat | OID4VCICredentialFormat): this { + this.format = format; + return this; + } + + public withSubjectIssuance(subjectIssuance: ExperimentalSubjectIssuance): this { + this.subjectIssuance = subjectIssuance; + return this; + } + + public withToken(accessToken: string): this { + this.token = accessToken; + return this; + } + + public withTokenFromResponse(response: AccessTokenResponse): this { + this.token = response.access_token; + return this; + } + + public withVersion(version: OpenId4VCIVersion): this { + this.version = version; + return this; + } + + public build(): CredentialRequestClient { + if (!this.version) { + this.withVersion(OpenId4VCIVersion.VER_1_0_11); + } + return new CredentialRequestClient(this); + } +} diff --git a/packages/client/lib/OpenID4VCIClient.ts b/packages/client/lib/OpenID4VCIClient.ts index e69cc7a4..c726c6b6 100644 --- a/packages/client/lib/OpenID4VCIClient.ts +++ b/packages/client/lib/OpenID4VCIClient.ts @@ -41,7 +41,7 @@ import { createAuthorizationRequestUrl } from './AuthorizationCodeClient'; import { createAuthorizationRequestUrlV1_0_11 } from './AuthorizationCodeClientV1_0_11'; import { CredentialOfferClient } from './CredentialOfferClient'; import { CredentialRequestOpts } from './CredentialRequestClient'; -import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder'; +import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13'; import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11'; import { MetadataClient } from './MetadataClient'; import { OpenID4VCIClientStateV1_0_11 } from './OpenID4VCIClientV1_0_11'; @@ -368,7 +368,7 @@ export class OpenID4VCIClient { if (jwk) this._state.jwk = jwk; if (kid) this._state.kid = kid; - let requestBuilder: CredentialRequestClientBuilder | CredentialRequestClientBuilderV1_0_11; + let requestBuilder: CredentialRequestClientBuilderV1_0_13 | CredentialRequestClientBuilderV1_0_11; if (this.version() < OpenId4VCIVersion.VER_1_0_13) { requestBuilder = this.credentialOffer ? CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({ @@ -383,11 +383,11 @@ export class OpenID4VCIClient { }); } else { requestBuilder = this.credentialOffer - ? CredentialRequestClientBuilder.fromCredentialOffer({ + ? CredentialRequestClientBuilderV1_0_13.fromCredentialOffer({ credentialOffer: this.credentialOffer, metadata: this.endpointMetadata, }) - : CredentialRequestClientBuilder.fromCredentialIssuer({ + : CredentialRequestClientBuilderV1_0_13.fromCredentialIssuer({ credentialIssuer: this.getIssuer(), credentialTypes, metadata: this.endpointMetadata, diff --git a/packages/client/lib/__tests__/CredentialRequestClient.spec.ts b/packages/client/lib/__tests__/CredentialRequestClient.spec.ts index e4fb5bac..7d2f176a 100644 --- a/packages/client/lib/__tests__/CredentialRequestClient.spec.ts +++ b/packages/client/lib/__tests__/CredentialRequestClient.spec.ts @@ -20,13 +20,8 @@ import * as jose from 'jose'; // @ts-ignore import nock from 'nock'; -import { - CredentialOfferClientV1_0_11, - CredentialRequestClientBuilder, - CredentialRequestClientBuilderV1_0_11, - MetadataClientV1_0_11, - ProofOfPossessionBuilder, -} from '..'; +import { CredentialOfferClient, CredentialRequestClientBuilderV1_0_13, MetadataClient, ProofOfPossessionBuilder } from '..'; +import { CredentialRequestClientBuilder } from '../CredentialRequestClientBuilder'; import { IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA, INITIATION_TEST, WALT_OID4VCI_METADATA } from './MetadataMocks'; import { getMockData } from './data/VciDataFixtures'; @@ -119,7 +114,9 @@ describe('Credential Request Client ', () => { }); it('should fail with invalid url', async () => { - const credReqClient = CredentialRequestClientBuilder.fromCredentialOfferRequest({ request: INITIATION_TEST }) + const credReqClient = ( + CredentialRequestClientBuilder.fromCredentialOfferRequest({ request: INITIATION_TEST }) as CredentialRequestClientBuilderV1_0_13 + ) .withCredentialEndpoint('httpsf://oidc4vci.demo.spruceid.com/credential') .withFormat('jwt_vc') .withCredentialIdentifier('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') @@ -154,14 +151,14 @@ describe('Credential Request Client with Walt.id ', () => { nock.cleanAll(); const WALT_IRR_URI = 'openid-initiate-issuance://?issuer=https%3A%2F%2Fjff.walt.id%2Fissuer-api%2Foidc%2F&credential_type=OpenBadgeCredential&pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhOTUyZjUxNi1jYWVmLTQ4YjMtODIxYy00OTRkYzgyNjljZjAiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.YE5DlalcLC2ChGEg47CQDaN1gTxbaQqSclIVqsSAUHE&user_pin_required=false'; - const credentialOffer = await CredentialOfferClientV1_0_11.fromURI(WALT_IRR_URI); + const credentialOffer = await CredentialOfferClient.fromURI(WALT_IRR_URI); const request = credentialOffer.credential_offer; - const metadata = await MetadataClientV1_0_11.retrieveAllMetadata(getIssuerFromCredentialOfferPayload(request) as string); + const metadata = await MetadataClient.retrieveAllMetadata(getIssuerFromCredentialOfferPayload(request) as string); expect(metadata.credential_endpoint).toEqual(WALT_OID4VCI_METADATA.credential_endpoint); expect(metadata.token_endpoint).toEqual(WALT_OID4VCI_METADATA.token_endpoint); - const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({ + const credReqClient = CredentialRequestClientBuilder.fromCredentialOffer({ credentialOffer, metadata, }).build(); @@ -205,7 +202,7 @@ describe('Credential Request Client with different issuers ', () => { const IRR_URI = 'openid-initiate-issuance://?issuer=https%3A%2F%2Fjff.walt.id%2Fissuer-api%2Fdefault%2Foidc%2F&credential_type=OpenBadgeCredential&pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwMTc4OTNjYy04ZTY3LTQxNzItYWZlOS1lODcyYmYxNDBlNWMiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.ODfq2AIhOcB61dAb3zMrXBJjPJaf53zkeHh_AssYyYA&user_pin_required=false'; const credentialOffer = await ( - await CredentialRequestClientBuilderV1_0_11.fromURI({ + await CredentialRequestClientBuilder.fromURI({ uri: IRR_URI, metadata: getMockData('walt')?.metadata as unknown as EndpointMetadata, }) @@ -250,7 +247,7 @@ describe('Credential Request Client with different issuers ', () => { const IRR_URI = 'openid-initiate-issuance://?issuer=https://launchpad.mattrlabs.com&credential_type=OpenBadgeCredential&pre-authorized_code=g0UCOj6RAN5AwHU6gczm_GzB4_lH6GW39Z0Dl2DOOiO'; const credentialOffer = await ( - await CredentialRequestClientBuilderV1_0_11.fromURI({ + await CredentialRequestClientBuilder.fromURI({ uri: IRR_URI, metadata: getMockData('mattr')?.metadata as unknown as EndpointMetadata, }) @@ -273,7 +270,7 @@ describe('Credential Request Client with different issuers ', () => { const IRR_URI = 'openid-initiate-issuance://?issuer=https://oidc4vc.diwala.io&credential_type=OpenBadgeCredential&pre-authorized_code=eyJhbGciOiJIUzI1NiJ9.eyJjcmVkZW50aWFsX3R5cGUiOiJPcGVuQmFkZ2VDcmVkZW50aWFsIiwiZXhwIjoxNjgxOTg0NDY3fQ.fEAHKz2nuWfiYHw406iNxr-81pWkNkbi31bWsYSf6Ng'; const credentialOffer = await ( - await CredentialRequestClientBuilderV1_0_11.fromURI({ + await CredentialRequestClientBuilder.fromURI({ uri: IRR_URI, metadata: getMockData('diwala')?.metadata as unknown as EndpointMetadata, }) diff --git a/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts b/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts index b29576fc..0087e465 100644 --- a/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts +++ b/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts @@ -11,7 +11,8 @@ import { } from '@sphereon/oid4vci-common'; import * as jose from 'jose'; -import { CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '..'; +import { CredentialRequestClientBuilderV1_0_13, ProofOfPossessionBuilder } from '..'; +import { CredentialRequestClientBuilder } from '../CredentialRequestClientBuilder'; import { IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA, INITIATION_TEST_URI, WALT_ISSUER_URL, WALT_OID4VCI_METADATA } from './MetadataMocks'; @@ -81,7 +82,7 @@ async function proofOfPossessionVerifierCallbackFunction(args: { jwt: string; ki describe('Credential Request Client Builder', () => { it('should build correctly provided with correct params', async function () { - const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI })) + const credReqClient = ((await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI })) as CredentialRequestClientBuilderV1_0_13) .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') .withFormat('jwt_vc') .withCredentialIdentifier('credentialType') @@ -94,7 +95,7 @@ describe('Credential Request Client Builder', () => { }); it('should build credential request correctly', async () => { - const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI })) + const credReqClient = ((await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI })) as CredentialRequestClientBuilderV1_0_13) .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') .withFormat('jwt_vc') .withCredentialIdentifier('OpenBadgeCredential') @@ -125,10 +126,10 @@ describe('Credential Request Client Builder', () => { it('should build credential request correctly without did', async () => { const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI })) - .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') - .withFormat('jwt_vc') - .withCredentialType('OpenBadgeCredential') - .build(); + .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') + .withFormat('jwt_vc') + .withCredentialType('OpenBadgeCredential') + .build(); const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({ jwt: jwtv1_0_13_withoutDid, callbacks: { @@ -137,9 +138,9 @@ describe('Credential Request Client Builder', () => { }, version: OpenId4VCIVersion.VER_1_0_13, }) - .withClientId('sphereon:wallet') - .withKid(kid_withoutDid) - .build(); + .withClientId('sphereon:wallet') + .withKid(kid_withoutDid) + .build(); await proofOfPossessionVerifierCallbackFunction({ ...proof, kid: kid_withoutDid }); const credentialRequest: CredentialRequestV1_0_13 = await credReqClient.createCredentialRequest({ proofInput: proof, diff --git a/packages/client/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts b/packages/client/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts index a11f417e..ebdda679 100644 --- a/packages/client/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts +++ b/packages/client/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts @@ -118,10 +118,10 @@ describe('Credential Request Client ', () => { }); const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({ credentialOffer: INITIATION_TEST_V1_0_08 }) - .withCredentialEndpoint(basePath + '/credential') - .withFormat('ldp_vc') - .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') - .build(); + .withCredentialEndpoint(basePath + '/credential') + .withFormat('ldp_vc') + .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') + .build(); const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({ jwt: jwt_withoutDid, callbacks: { @@ -129,10 +129,10 @@ describe('Credential Request Client ', () => { }, version: OpenId4VCIVersion.VER_1_0_08, }) - // .withEndpointMetadata(metadata) - .withClientId('sphereon:wallet') - .withKid(kid_withoutDid) - .build(); + // .withEndpointMetadata(metadata) + .withClientId('sphereon:wallet') + .withKid(kid_withoutDid) + .build(); expect(credReqClient.getCredentialEndpoint()).toEqual(basePath + '/credential'); const credentialRequest = await credReqClient.createCredentialRequest({ proofInput: proof, version: OpenId4VCIVersion.VER_1_0_08 }); expect(credentialRequest.proof?.jwt?.includes(partialJWT_withoutDid)).toBeTruthy(); @@ -180,16 +180,16 @@ describe('Credential Request Client ', () => { const mockedVC = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly9leGFtcGxlLmVkdS9pc3N1ZXJzLzU2NTA0OSIsImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMDA6MDA6MDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJlYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSIsImlhdCI6MTcxODM1NzcxOH0.7iiOTuIjQRyrIincYyDW6m0nBYmDoYfXcTYFrywsKEY'; nock('https://oidc4vci.demo.spruceid.com') - .post(/credential/) - .reply(200, { - format: 'jwt-vc', - credential: mockedVC, - }); + .post(/credential/) + .reply(200, { + format: 'jwt-vc', + credential: mockedVC, + }); const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest({ request: INITIATION_TEST }) - .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') - .withFormat('jwt_vc') - .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') - .build(); + .withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential') + .withFormat('jwt_vc') + .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') + .build(); const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({ jwt: jwt_withoutDid, callbacks: { @@ -197,10 +197,10 @@ describe('Credential Request Client ', () => { }, version: OpenId4VCIVersion.VER_1_0_08, }) - // .withEndpointMetadata(metadata) - .withKid(kid_withoutDid) - .withClientId('sphereon:wallet') - .build(); + // .withEndpointMetadata(metadata) + .withKid(kid_withoutDid) + .withClientId('sphereon:wallet') + .build(); const credentialRequest = await credReqClient.createCredentialRequest({ proofInput: proof, format: 'jwt', @@ -236,10 +236,10 @@ describe('Credential Request Client ', () => { it('should fail with invalid url without did', async () => { const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest({ request: INITIATION_TEST }) - .withCredentialEndpoint('httpsf://oidc4vci.demo.spruceid.com/credential') - .withFormat('jwt_vc') - .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') - .build(); + .withCredentialEndpoint('httpsf://oidc4vci.demo.spruceid.com/credential') + .withFormat('jwt_vc') + .withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential') + .build(); const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({ jwt: jwt_withoutDid, callbacks: { @@ -247,10 +247,10 @@ describe('Credential Request Client ', () => { }, version: OpenId4VCIVersion.VER_1_0_08, }) - // .withEndpointMetadata(metadata) - .withKid(kid_withoutDid) - .withClientId('sphereon:wallet') - .build(); + // .withEndpointMetadata(metadata) + .withKid(kid_withoutDid) + .withClientId('sphereon:wallet') + .build(); await expect(credReqClient.acquireCredentialsUsingRequest({ format: 'jwt_vc_json', types: ['random'], proof })).rejects.toThrow( Error(URL_NOT_VALID), ); diff --git a/packages/client/lib/__tests__/IT.spec.ts b/packages/client/lib/__tests__/IT.spec.ts index eeccc7f2..4b9242ad 100644 --- a/packages/client/lib/__tests__/IT.spec.ts +++ b/packages/client/lib/__tests__/IT.spec.ts @@ -11,21 +11,17 @@ import { // @ts-ignore import nock from 'nock'; -import { - AccessTokenClient, AccessTokenClientV1_0_11, CredentialOfferClientV1_0_13, - CredentialRequestClientBuilder, CredentialRequestClientBuilderV1_0_11, - OpenID4VCIClient, OpenID4VCIClientV1_0_13, - ProofOfPossessionBuilder -} from '..' +import { AccessTokenClient, AccessTokenClientV1_0_11, OpenID4VCIClient, OpenID4VCIClientV1_0_13, ProofOfPossessionBuilder } from '..'; import { CredentialOfferClient } from '../CredentialOfferClient'; +import { CredentialRequestClientBuilder } from '../CredentialRequestClientBuilder'; import { IDENTIPROOF_AS_METADATA, IDENTIPROOF_AS_URL, IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA, - IDENTIPROOF_OID4VCI_METADATA_v13 -} from './MetadataMocks' + IDENTIPROOF_OID4VCI_METADATA_v13, +} from './MetadataMocks'; export const UNIT_TEST_TIMEOUT = 30000; @@ -73,8 +69,8 @@ describe('OID4VCI-Client should', () => { 'https://issuer.research.identiproof.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22eyJhbGciOiJSU0Et...FYUaBy%22%7D%7D%7D'; const HTTPS_OFFER_QR_PRE_AUTHORIZED = 'https://issuer.research.identiproof.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%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%22adhjhdjajkdkhjhdj%22%2C%22user_pin_required%22%3Atrue%7D%7D%7D'; -const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = - 'https://issuer.research.identiproof.io?credential_offer=%7B%0A%20%20%20%20%22credential_issuer%22%3A%20%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%0A%20%20%20%20%22credential_configuration_ids%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%22UniversityDegreeCredential%22%0A%20%20%20%20%5D%2C%0A%20%20%20%20%22grants%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22pre-authorized_code%22%3A%20%22adhjhdjajkdkhjhdj%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22tx_code%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22length%22%3A%204%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22input_mode%22%3A%20%22numeric%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22description%22%3A%20%22Please%20provide%20the%20one-time%20code%20that%20was%20sent%20via%20e-mail%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%7D'; + const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = + 'https://issuer.research.identiproof.io?credential_offer=%7B%0A%20%20%20%20%22credential_issuer%22%3A%20%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%0A%20%20%20%20%22credential_configuration_ids%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%22UniversityDegreeCredential%22%0A%20%20%20%20%5D%2C%0A%20%20%20%20%22grants%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22pre-authorized_code%22%3A%20%22adhjhdjajkdkhjhdj%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22tx_code%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22length%22%3A%204%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22input_mode%22%3A%20%22numeric%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22description%22%3A%20%22Please%20provide%20the%20one-time%20code%20that%20was%20sent%20via%20e-mail%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%7D'; const INITIATE_QR_V1_0_13 = 'openid-credential-offer://?credential_offer=%7B%22credential_issuer%22:%22https://issuer.research.identiproof.io%22,%22credential_configuration_ids%22:%5B%22OpenBadgeCredentialUrl%22%5D,%22grants%22:%7B%22urn:ietf:params:oauth:grant-type:pre-authorized_code%22:%7B%22pre-authorized_code%22:%22oaKazRN8I0IbtZ0C7JuMn5%22,%22tx_code%22:%7B%22input_mode%22:%22text%22,%22length%22:22,%22description%22:%22Please%20enter%20the%20serial%20number%20of%20your%20physical%20drivers%20license%22%7D%7D%7D%7D'; @@ -118,7 +114,7 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = }); it('succeed with a full flow with the client using OpenID4VCI draft < 9 and https', async () => { - succeedWithAFullFlowWithClientSetup() + succeedWithAFullFlowWithClientSetup(); const client = await OpenID4VCIClient.fromURI({ uri: HTTPS_INITIATE_QR, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', @@ -129,7 +125,7 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = }); it('should succeed with a full flow with the client using OpenID4VCI draft > 11, https and authorization_code flow', async () => { - succeedWithAFullFlowWithClientSetup() + succeedWithAFullFlowWithClientSetup(); const client = await OpenID4VCIClient.fromURI({ uri: HTTPS_OFFER_QR_AUTHORIZATION_CODE, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', @@ -140,7 +136,7 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = }); it('should succeed with a full flow with the client using OpenID4VCI draft > 11, https and preauthorized_code flow', async () => { - succeedWithAFullFlowWithClientSetup() + succeedWithAFullFlowWithClientSetup(); const client = await OpenID4VCIClient.fromURI({ uri: HTTPS_OFFER_QR_PRE_AUTHORIZED, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', @@ -151,10 +147,12 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = }); it('should succeed with a full flow with the client using OpenID4VCI draft >= 13, https and preauthorized_code flow without did', async () => { - nock(IDENTIPROOF_ISSUER_URL).get(WellKnownEndpoints.OPENID_CONFIGURATION).reply(200, { - token_endpoint: `${IDENTIPROOF_ISSUER_URL}/token`, - authorization_endpoint: `${IDENTIPROOF_ISSUER_URL}/authorize`, - }); + nock(IDENTIPROOF_ISSUER_URL) + .get(WellKnownEndpoints.OPENID_CONFIGURATION) + .reply(200, { + token_endpoint: `${IDENTIPROOF_ISSUER_URL}/token`, + authorization_endpoint: `${IDENTIPROOF_ISSUER_URL}/authorize`, + }); nock(IDENTIPROOF_ISSUER_URL).post('/token').reply(200, { access_token: 'ey6546.546654.64565', authorization_pending: false, @@ -162,14 +160,14 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = c_nonce_expires_in: 2025101300, interval: 2025101300, token_type: 'Bearer', - }) + }); nock(IDENTIPROOF_ISSUER_URL).get('/.well-known/openid-credential-issuer').reply(200, JSON.stringify(IDENTIPROOF_OID4VCI_METADATA_v13)); nock(ISSUER_URL) - .post(/credential/) - .reply(200, { - format: 'jwt-vc', - credential: mockedVC, - }); + .post(/credential/) + .reply(200, { + format: 'jwt-vc', + credential: mockedVC, + }); const client = await OpenID4VCIClientV1_0_13.fromURI({ uri: HTTPS_OFFER_QR_PRE_AUTHORIZED_v13, kid: 'ebfeb1f712ebc6f1c276e12ec21/keys/1', @@ -196,7 +194,7 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = }); it('should succeed with a full flow with the client using OpenID4VCI draft > 11, https and preauthorized_code flow', async () => { - succeedWithAFullFlowWithClientSetup() + succeedWithAFullFlowWithClientSetup(); const client = await OpenID4VCIClient.fromURI({ uri: HTTPS_OFFER_QR_PRE_AUTHORIZED, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', @@ -256,7 +254,7 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = format: 'jwt-vc', credential: mockedVC, }); - const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({ credentialOffer: credentialOffer }) + const credReqClient = CredentialRequestClientBuilder.fromCredentialOffer({ credentialOffer: credentialOffer }) .withFormat('jwt_vc') .withTokenFromResponse(accessTokenResponse.successBody!) @@ -290,7 +288,7 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = 'succeed with a full flow without the client and without did', async () => { /* Convert the URI into an object */ - const credentialOffer: CredentialOfferRequestWithBaseUrl = await CredentialOfferClientV1_0_13.fromURI(INITIATE_QR_V1_0_13); + const credentialOffer: CredentialOfferRequestWithBaseUrl = await CredentialOfferClient.fromURI(INITIATE_QR_V1_0_13); expect(credentialOffer.baseUrl).toEqual('openid-credential-offer://'); expect(credentialOffer.original_credential_offer).toEqual({ @@ -298,19 +296,19 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = credential_issuer: ISSUER_URL, grants: { 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { - 'pre-authorized_code': "oaKazRN8I0IbtZ0C7JuMn5", + 'pre-authorized_code': 'oaKazRN8I0IbtZ0C7JuMn5', tx_code: { description: 'Please enter the serial number of your physical drivers license', input_mode: 'text', length: 22, }, }, - } + }, }); nock(ISSUER_URL) - .post(/token.*/) - .reply(200, JSON.stringify(mockedAccessTokenResponse)); + .post(/token.*/) + .reply(200, JSON.stringify(mockedAccessTokenResponse)); /* The actual access token calls */ const accessTokenClient: AccessTokenClient = new AccessTokenClient(); @@ -318,16 +316,16 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = expect(accessTokenResponse.successBody).toEqual(mockedAccessTokenResponse); // Get the credential nock(ISSUER_URL) - .post(/credential/) - .reply(200, { - format: 'jwt-vc', - credential: mockedVC, - }); + .post(/credential/) + .reply(200, { + format: 'jwt-vc', + credential: mockedVC, + }); const credReqClient = CredentialRequestClientBuilder.fromCredentialOffer({ credentialOffer: credentialOffer }) - .withFormat('jwt_vc') + .withFormat('jwt_vc') - .withTokenFromResponse(accessTokenResponse.successBody!) - .build(); + .withTokenFromResponse(accessTokenResponse.successBody!) + .build(); //TS2322: Type '(args: ProofOfPossessionCallbackArgs) => Promise' // is not assignable to type 'ProofOfPossessionCallback'. @@ -340,14 +338,14 @@ const HTTPS_OFFER_QR_PRE_AUTHORIZED_v13 = }, version: OpenId4VCIVersion.VER_1_0_13, }) - .withEndpointMetadata({ - issuer: 'https://issuer.research.identiproof.io', - credential_endpoint: 'https://issuer.research.identiproof.io/credential', - token_endpoint: 'https://issuer.research.identiproof.io/token', - }) - .withKid('ebfeb1f712ebc6f1c276e12ec21/keys/1') - .build(); - const credResponse = await credReqClient.acquireCredentialsUsingProof({ proofInput: proof, credentialIdentifier: 'OpenBadgeCredentialUrl'}); + .withEndpointMetadata({ + issuer: 'https://issuer.research.identiproof.io', + credential_endpoint: 'https://issuer.research.identiproof.io/credential', + token_endpoint: 'https://issuer.research.identiproof.io/token', + }) + .withKid('ebfeb1f712ebc6f1c276e12ec21/keys/1') + .build(); + const credResponse = await credReqClient.acquireCredentialsUsingProof({ proofInput: proof, credentialIdentifier: 'OpenBadgeCredentialUrl' }); expect(credResponse.successBody?.credential).toEqual(mockedVC); }, UNIT_TEST_TIMEOUT, diff --git a/packages/client/lib/index.ts b/packages/client/lib/index.ts index 4b422e25..8b959462 100644 --- a/packages/client/lib/index.ts +++ b/packages/client/lib/index.ts @@ -13,6 +13,7 @@ export * from './CredentialOfferClientV1_0_11'; export * from './CredentialOfferClientV1_0_13'; export * from './CredentialRequestClientV1_0_11'; export * from './CredentialRequestClientBuilder'; +export * from './CredentialRequestClientBuilderV1_0_13'; export * from './CredentialRequestClientBuilderV1_0_11'; export * from './functions'; export * from './MetadataClient'; diff --git a/packages/common/lib/types/Generic.types.ts b/packages/common/lib/types/Generic.types.ts index daad64b6..a1912208 100644 --- a/packages/common/lib/types/Generic.types.ts +++ b/packages/common/lib/types/Generic.types.ts @@ -62,7 +62,7 @@ export interface CredentialSupplierConfig { export interface CredentialIssuerMetadataOpts { credential_endpoint?: string; // REQUIRED. URL of the Credential Issuer's Credential Endpoint. This URL MUST use the https scheme and MAY contain port, path and query parameter components. batch_credential_endpoint?: string; // OPTIONAL. URL of the Credential Issuer's Batch Credential Endpoint. This URL MUST use the https scheme and MAY contain port, path and query parameter components. If omitted, the Credential Issuer does not support the Batch Credential Endpoint. - credentials_supported: CredentialConfigurationSupported[]; // REQUIRED. A JSON array containing a list of JSON objects, each of them representing metadata about a separate credential type that the Credential Issuer can issue. The JSON objects in the array MUST conform to the structure of the Section 10.2.3.1. + credentials_supported?: CredentialConfigurationSupported[]; // REQUIRED in versions below 13. A JSON array containing a list of JSON objects, each of them representing metadata about a separate credential type that the Credential Issuer can issue. The JSON objects in the array MUST conform to the structure of the Section 10.2.3.1. credential_issuer: string; // REQUIRED. The Credential Issuer's identifier. authorization_server?: string; // OPTIONAL. Identifier of the OAuth 2.0 Authorization Server (as defined in [RFC8414]) the Credential Issuer relies on for authorization. If this element is omitted, the entity providing the Credential Issuer is also acting as the AS, i.e. the Credential Issuer's identifier is used as the OAuth 2.0 Issuer value to obtain the Authorization Server metadata as per [RFC8414]. token_endpoint?: string;