From 0e3c7e9570dc0bb0ce409d2669e9559b0f8fee0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linas=20Is=CC=8Cganaitis?= Date: Fri, 6 Oct 2023 12:43:06 +0200 Subject: [PATCH 1/3] Enable authorization code flow helper methods. Fix building authorization URI methods. --- packages/client/lib/AccessTokenClient.ts | 36 +++++++------- packages/client/lib/OpenID4VCIClient.ts | 57 ++++++++++++--------- packages/common/lib/functions/Encoding.ts | 60 +++++++++++------------ 3 files changed, 80 insertions(+), 73 deletions(-) diff --git a/packages/client/lib/AccessTokenClient.ts b/packages/client/lib/AccessTokenClient.ts index 5b669e39..bab1788f 100644 --- a/packages/client/lib/AccessTokenClient.ts +++ b/packages/client/lib/AccessTokenClient.ts @@ -4,10 +4,10 @@ import { AccessTokenResponse, assertedUniformCredentialOffer, AuthorizationServerOpts, + AuthzFlowType, EndpointMetadata, getIssuerFromCredentialOfferPayload, GrantTypes, - isPreAuthCode, IssuerOpts, OpenIDResponse, PRE_AUTH_CODE_LITERAL, @@ -67,6 +67,7 @@ export class AccessTokenClient { issuerOpts?: IssuerOpts; }): Promise> { this.validate(accessTokenRequest, isPinRequired); + const requestTokenURL = AccessTokenClient.determineTokenURL({ asOpts, issuerOpts, @@ -76,6 +77,7 @@ export class AccessTokenClient { ? await MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false }) : undefined, }); + return this.sendAuthCode(requestTokenURL, accessTokenRequest); } @@ -83,38 +85,36 @@ export class AccessTokenClient { const { asOpts, pin, codeVerifier, code, redirectUri } = opts; const credentialOfferRequest = await toUniformCredentialOfferRequest(opts.credentialOffer); const request: Partial = {}; + if (asOpts?.clientId) { request.client_id = asOpts.clientId; } - this.assertNumericPin(this.isPinRequiredValue(credentialOfferRequest.credential_offer), pin); - request.user_pin = pin; + if (credentialOfferRequest.supportedFlows.includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) { + this.assertNumericPin(this.isPinRequiredValue(credentialOfferRequest.credential_offer), pin); + request.user_pin = pin; - const isPreAuth = isPreAuthCode(credentialOfferRequest); - if (isPreAuth) { - if (codeVerifier) { - throw new Error('Cannot pass a code_verifier when flow type is pre-authorized'); - } request.grant_type = GrantTypes.PRE_AUTHORIZED_CODE; // we actually know it is there because of the isPreAuthCode call request[PRE_AUTH_CODE_LITERAL] = credentialOfferRequest?.credential_offer.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.[PRE_AUTH_CODE_LITERAL]; + + return request as AccessTokenRequest; } - if (!isPreAuth && credentialOfferRequest.credential_offer.grants?.authorization_code?.issuer_state) { - this.throwNotSupportedFlow(); // not supported yet + + if (credentialOfferRequest.supportedFlows.includes(AuthzFlowType.AUTHORIZATION_CODE_FLOW)) { request.grant_type = GrantTypes.AUTHORIZATION_CODE; - } - if (codeVerifier) { - request.code_verifier = codeVerifier; request.code = code; request.redirect_uri = redirectUri; - request.grant_type = GrantTypes.AUTHORIZATION_CODE; - } - if (request.grant_type === GrantTypes.AUTHORIZATION_CODE && isPreAuth) { - throw Error('A pre_authorized_code flow cannot have an issuer state in the credential offer'); + + if (codeVerifier) { + request.code_verifier = codeVerifier; + } + + return request as AccessTokenRequest; } - return request as AccessTokenRequest; + throw new Error('Credential offer request does not follow neither pre-authorized code nor authorization code flow requirements.'); } private assertPreAuthorizedGrantType(grantType: GrantTypes): void { diff --git a/packages/client/lib/OpenID4VCIClient.ts b/packages/client/lib/OpenID4VCIClient.ts index 592d2dfd..32d85172 100644 --- a/packages/client/lib/OpenID4VCIClient.ts +++ b/packages/client/lib/OpenID4VCIClient.ts @@ -11,7 +11,6 @@ import { EndpointMetadataResult, OID4VCICredentialFormat, OpenId4VCIVersion, - OpenIDResponse, ProofOfPossessionCallbacks, PushedAuthorizationResponse, ResponseType, @@ -63,9 +62,6 @@ export class OpenID4VCIClient { alg?: Alg | string, clientId?: string, ) { - if (!credentialOffer.supportedFlows.includes(flowType)) { - throw Error(`Flows ${flowType} is not supported by issuer ${credentialOffer.credential_offer_uri}`); - } this._flowType = flowType; this._credentialOffer = credentialOffer; this._kid = kid; @@ -146,11 +142,12 @@ export class OpenID4VCIClient { authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)), redirect_uri: redirectUri, scope: scope, + issuer_state: this.credentialOffer.issuerState, } as AuthorizationRequestV1_0_09; return convertJsonToURI(queryObj, { baseUrl: this._endpointMetadata.authorization_endpoint, - uriTypeProperties: ['redirect_uri', 'scope', 'authorization_details'], + uriTypeProperties: ['redirect_uri', 'scope', 'authorization_details', 'issuer_state'], version: this.version(), }); } @@ -162,7 +159,7 @@ export class OpenID4VCIClient { authorizationDetails, redirectUri, scope, - }: AuthRequestOpts): Promise> { + }: AuthRequestOpts): Promise { // Scope and authorization_details can be used in the same authorization request // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param if (!scope && !authorizationDetails) { @@ -187,17 +184,27 @@ export class OpenID4VCIClient { scope = `openid ${scope}`; } - //fixme: handle this for v11 - const queryObj: AuthorizationRequestV1_0_09 = { + const queryObj = { response_type: ResponseType.AUTH_CODE, client_id: clientId, code_challenge_method: codeChallengeMethod, code_challenge: codeChallenge, authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)), redirect_uri: redirectUri, - scope: scope, + scope: scope || 'openid', + issuer_state: this.credentialOffer.issuerState || '', }; - return await formPost(parEndpoint, JSON.stringify(queryObj)); + + const response = await formPost(parEndpoint, new URLSearchParams(queryObj)); + + return convertJsonToURI( + { request_uri: response.successBody?.request_uri }, + { + baseUrl: this._endpointMetadata.authorization_endpoint, + uriTypeProperties: ['request_uri'], + version: this.version(), + }, + ); } public handleAuthorizationDetails(authorizationDetails?: AuthDetails | AuthDetails[]): AuthDetails | AuthDetails[] | undefined { @@ -237,7 +244,9 @@ export class OpenID4VCIClient { redirectUri?: string; }): Promise { const { pin, clientId, codeVerifier, code, redirectUri } = opts ?? {}; + this.assertIssuerData(); + if (clientId) { this._clientId = clientId; } @@ -300,24 +309,26 @@ export class OpenID4VCIClient { credentialOffer: this.credentialOffer, metadata: this.endpointMetadata, }); + requestBuilder.withTokenFromResponse(this.accessTokenResponse); if (this.endpointMetadata?.credentialIssuerMetadata) { const metadata = this.endpointMetadata.credentialIssuerMetadata; - const types = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes]; + const types = Array.isArray(credentialTypes) ? credentialTypes.sort() : [credentialTypes]; + if (metadata.credentials_supported && Array.isArray(metadata.credentials_supported)) { - for (const type of types) { - let typeSupported = false; - for (const credentialSupported of metadata.credentials_supported) { - if (!credentialSupported.types || credentialSupported.types.length === 0) { - throw Error('types is required in the credentials supported'); - } - if (credentialSupported.types.indexOf(type) != -1) { - typeSupported = true; - } + let typeSupported = false; + + metadata.credentials_supported.forEach((supportedCredential) => { + if (!supportedCredential.types || supportedCredential.types.length === 0) { + throw Error('types is required in the credentials supported'); } - if (!typeSupported) { - throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`); + if (supportedCredential.types.sort().every((t, i) => types[i] === t) || (types.length === 1 && types[0] === supportedCredential.id)) { + typeSupported = true; } + }); + + if (!typeSupported) { + throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`); } } else if (metadata.credentials_supported && !Array.isArray(metadata.credentials_supported)) { const credentialsSupported = metadata.credentials_supported as CredentialSupportedTypeV1_0_08; @@ -389,7 +400,7 @@ export class OpenID4VCIClient { result[0] = types; return result; } else { - return this.credentialOffer.credential_offer.credentials.map((c, index) => { + return this.credentialOffer.credential_offer.credentials.map((c) => { return typeof c === 'string' ? [c] : c.types; }); } diff --git a/packages/common/lib/functions/Encoding.ts b/packages/common/lib/functions/Encoding.ts index b84a918a..3e789765 100644 --- a/packages/common/lib/functions/Encoding.ts +++ b/packages/common/lib/functions/Encoding.ts @@ -1,4 +1,4 @@ -import { BAD_PARAMS, DecodeURIAsJsonOpts, EncodeJsonAsURIOpts, OpenId4VCIVersion, SearchValue } from '../types'; +import { BAD_PARAMS, DecodeURIAsJsonOpts, EncodeJsonAsURIOpts, SearchValue } from '../types'; /** * @function encodeJsonAsURI encodes a Json object into a URI @@ -29,39 +29,35 @@ export function convertJsonToURI( return encodeURIComponent(key.replace(' ', '')); } - let components: string; - if (opts?.version && opts.version > OpenId4VCIVersion.VER_1_0_08) { - // v11 changed from encoding every param to a encoded json object with a credential_offer param key - components = encodeAndStripWhitespace(JSON.stringify(json)); - } else { - for (const [key, value] of Object.entries(json)) { - if (!value) { - continue; - } - //Skip properties that are not of URL type - if (!opts?.uriTypeProperties?.includes(key)) { - results.push(`${key}=${value}`); - continue; - } - if (opts?.arrayTypeProperties?.includes(key) && Array.isArray(value)) { - results.push(value.map((v) => `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(v, /\./g)}`).join('&')); - continue; - } - const isBool = typeof value == 'boolean'; - const isNumber = typeof value == 'number'; - const isString = typeof value == 'string'; - let encoded; - if (isBool || isNumber) { - encoded = `${encodeAndStripWhitespace(key)}=${value}`; - } else if (isString) { - encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(value, /\./g)}`; - } else { - encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(JSON.stringify(value), /\./g)}`; - } - results.push(encoded); + for (const [key, value] of Object.entries(json)) { + if (!value) { + continue; + } + //Skip properties that are not of URL type + if (!opts?.uriTypeProperties?.includes(key)) { + results.push(`${key}=${value}`); + continue; + } + if (opts?.arrayTypeProperties?.includes(key) && Array.isArray(value)) { + results.push(value.map((v) => `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(v, /\./g)}`).join('&')); + continue; + } + const isBool = typeof value == 'boolean'; + const isNumber = typeof value == 'number'; + const isString = typeof value == 'string'; + let encoded; + if (isBool || isNumber) { + encoded = `${encodeAndStripWhitespace(key)}=${value}`; + } else if (isString) { + encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(value, /\./g)}`; + } else { + encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(JSON.stringify(value), /\./g)}`; } - components = results.join('&'); + results.push(encoded); } + + const components = results.join('&'); + if (opts?.baseUrl) { if (opts.baseUrl.endsWith('=')) { if (opts.param) { From 52f715f58edec7ba792db3484553b09f61912ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linas=20Is=CC=8Cganaitis?= Date: Fri, 6 Oct 2023 15:58:19 +0200 Subject: [PATCH 2/3] Remove flowType configuration, use clientId that was passed during client initialization, update tests --- packages/client/README.md | 1 - packages/client/lib/OpenID4VCIClient.ts | 70 +++++++------------ .../lib/__tests__/AccessTokenClient.spec.ts | 27 +------ packages/client/lib/__tests__/IT.spec.ts | 4 -- .../lib/__tests__/MattrE2E.spec.test.ts | 4 +- .../lib/__tests__/OpenID4VCIClient.spec.ts | 15 ++-- .../lib/__tests__/OpenID4VCIClientPAR.spec.ts | 22 +++--- packages/common/lib/functions/Encoding.ts | 60 ++++++++-------- .../lib/__tests__/ClientIssuerIT.spec.ts | 2 - .../issuer/lib/__tests__/VcIssuer.spec.ts | 3 +- 10 files changed, 75 insertions(+), 133 deletions(-) diff --git a/packages/client/README.md b/packages/client/README.md index ff28f488..b1da2a32 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -57,7 +57,6 @@ import { OpenID4VCIClient } from '@sphereon/oid4vci-client'; // The client is initiated from a URI. This URI is provided by the Issuer, typically as a URL or QR code. const client = await OpenID4VCIClient.fromURI({ 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', - flowType: AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW, // The flow to use kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#key-1', // Our DID. You can defer this also to when the acquireCredential method is called alg: Alg.ES256, // The signing Algorithm we will use. You can defer this also to when the acquireCredential method is called 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 diff --git a/packages/client/lib/OpenID4VCIClient.ts b/packages/client/lib/OpenID4VCIClient.ts index 32d85172..fe90709a 100644 --- a/packages/client/lib/OpenID4VCIClient.ts +++ b/packages/client/lib/OpenID4VCIClient.ts @@ -1,7 +1,6 @@ import { AccessTokenResponse, Alg, - AuthorizationRequestV1_0_09, AuthzFlowType, CodeChallengeMethod, CredentialOfferPayloadV1_0_08, @@ -38,7 +37,6 @@ interface AuthDetails { } interface AuthRequestOpts { - clientId: string; codeChallenge: string; codeChallengeMethod: CodeChallengeMethod; authorizationDetails?: AuthDetails | AuthDetails[]; @@ -47,7 +45,6 @@ interface AuthRequestOpts { } export class OpenID4VCIClient { - private readonly _flowType: AuthzFlowType; private readonly _credentialOffer: CredentialOfferRequestWithBaseUrl; private _clientId?: string; private _kid: string | undefined; @@ -55,14 +52,7 @@ export class OpenID4VCIClient { private _endpointMetadata: EndpointMetadataResult | undefined; private _accessTokenResponse: AccessTokenResponse | undefined; - private constructor( - credentialOffer: CredentialOfferRequestWithBaseUrl, - flowType: AuthzFlowType, - kid?: string, - alg?: Alg | string, - clientId?: string, - ) { - this._flowType = flowType; + private constructor(credentialOffer: CredentialOfferRequestWithBaseUrl, kid?: string, alg?: Alg | string, clientId?: string) { this._credentialOffer = credentialOffer; this._kid = kid; this._alg = alg; @@ -71,7 +61,6 @@ export class OpenID4VCIClient { public static async fromURI({ uri, - flowType, kid, alg, retrieveServerMetadata, @@ -79,14 +68,13 @@ export class OpenID4VCIClient { resolveOfferUri, }: { uri: string; - flowType: AuthzFlowType; kid?: string; alg?: Alg | string; retrieveServerMetadata?: boolean; resolveOfferUri?: boolean; clientId?: string; }): Promise { - const client = new OpenID4VCIClient(await CredentialOfferClient.fromURI(uri, { resolve: resolveOfferUri }), flowType, kid, alg, clientId); + const client = new OpenID4VCIClient(await CredentialOfferClient.fromURI(uri, { resolve: resolveOfferUri }), kid, alg, clientId); if (retrieveServerMetadata === undefined || retrieveServerMetadata) { await client.retrieveServerMetadata(); @@ -102,14 +90,7 @@ export class OpenID4VCIClient { return this.endpointMetadata; } - public createAuthorizationRequestUrl({ - clientId, - codeChallengeMethod, - codeChallenge, - authorizationDetails, - redirectUri, - scope, - }: AuthRequestOpts): string { + public createAuthorizationRequestUrl({ codeChallengeMethod, codeChallenge, authorizationDetails, redirectUri, scope }: AuthRequestOpts): string { // Scope and authorization_details can be used in the same authorization request // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param if (!scope && !authorizationDetails) { @@ -129,31 +110,31 @@ export class OpenID4VCIClient { } // add 'openid' scope if not present - if (scope && !scope.includes('openid')) { - scope = `openid ${scope}`; + if (!scope?.includes('openid')) { + scope = ['openid', scope].filter((s) => !!s).join(' '); } - //fixme: handle this for v11 - const queryObj = { + const queryObj: { [key: string]: string } = { response_type: ResponseType.AUTH_CODE, - client_id: clientId, + client_id: this.clientId || '', code_challenge_method: codeChallengeMethod, code_challenge: codeChallenge, authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)), redirect_uri: redirectUri, scope: scope, - issuer_state: this.credentialOffer.issuerState, - } as AuthorizationRequestV1_0_09; + }; + + if (this.credentialOffer.issuerState) { + queryObj['issuer_state'] = this.credentialOffer.issuerState; + } return convertJsonToURI(queryObj, { baseUrl: this._endpointMetadata.authorization_endpoint, uriTypeProperties: ['redirect_uri', 'scope', 'authorization_details', 'issuer_state'], - version: this.version(), }); } public async acquirePushedAuthorizationRequestURI({ - clientId, codeChallengeMethod, codeChallenge, authorizationDetails, @@ -180,29 +161,31 @@ export class OpenID4VCIClient { const parEndpoint: string = this._endpointMetadata.credentialIssuerMetadata.pushed_authorization_request_endpoint; // add 'openid' scope if not present - if (scope && !scope.includes('openid')) { - scope = `openid ${scope}`; + if (!scope?.includes('openid')) { + scope = ['openid', scope].filter((s) => !!s).join(' '); } - const queryObj = { + const queryObj: { [key: string]: string } = { response_type: ResponseType.AUTH_CODE, - client_id: clientId, + client_id: this.clientId || '', code_challenge_method: codeChallengeMethod, code_challenge: codeChallenge, authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)), redirect_uri: redirectUri, - scope: scope || 'openid', - issuer_state: this.credentialOffer.issuerState || '', + scope: scope, }; + if (this.credentialOffer.issuerState) { + queryObj['issuer_state'] = this.credentialOffer.issuerState; + } + const response = await formPost(parEndpoint, new URLSearchParams(queryObj)); return convertJsonToURI( { request_uri: response.successBody?.request_uri }, { - baseUrl: this._endpointMetadata.authorization_endpoint, + baseUrl: this._endpointMetadata.credentialIssuerMetadata.authorization_endpoint, uriTypeProperties: ['request_uri'], - version: this.version(), }, ); } @@ -322,7 +305,10 @@ export class OpenID4VCIClient { if (!supportedCredential.types || supportedCredential.types.length === 0) { throw Error('types is required in the credentials supported'); } - if (supportedCredential.types.sort().every((t, i) => types[i] === t) || (types.length === 1 && types[0] === supportedCredential.id)) { + if ( + supportedCredential.types.sort().every((t, i) => types[i] === t) || + (types.length === 1 && (types[0] === supportedCredential.id || supportedCredential.types.includes(types[0]))) + ) { typeSupported = true; } }); @@ -406,10 +392,6 @@ export class OpenID4VCIClient { } } - get flowType(): AuthzFlowType { - return this._flowType; - } - issuerSupportedFlowTypes(): AuthzFlowType[] { return this.credentialOffer.supportedFlows; } diff --git a/packages/client/lib/__tests__/AccessTokenClient.spec.ts b/packages/client/lib/__tests__/AccessTokenClient.spec.ts index 52b7345a..045b2148 100644 --- a/packages/client/lib/__tests__/AccessTokenClient.spec.ts +++ b/packages/client/lib/__tests__/AccessTokenClient.spec.ts @@ -1,11 +1,4 @@ -import { - AccessTokenRequest, - AccessTokenRequestOpts, - AccessTokenResponse, - GrantTypes, - OpenIDResponse, - WellKnownEndpoints, -} from '@sphereon/oid4vci-common'; +import { AccessTokenRequest, AccessTokenResponse, GrantTypes, OpenIDResponse, WellKnownEndpoints } from '@sphereon/oid4vci-common'; import nock from 'nock'; import { AccessTokenClient } from '../AccessTokenClient'; @@ -204,24 +197,6 @@ describe('AccessTokenClient should', () => { ).rejects.toThrow(Error('Cannot set a pin, when the pin is not required.')); }); - it('get error if code_verifier is present when flow type is pre-authorized', async () => { - const accessTokenClient: AccessTokenClient = new AccessTokenClient(); - - nock(MOCK_URL).post(/.*/).reply(200, {}); - - const requestOpts: AccessTokenRequestOpts = { - credentialOffer: INITIATION_TEST, - pin: undefined, - codeVerifier: 'RylyWGQ-dzpObnEcoMBDIH9cTAwZXk1wYzktKxsOFgA', - code: 'LWCt225yj7gzT2cWeMP4hXj4B4oIYkEiGs4T6pfez91', - redirectUri: 'http://example.com/cb', - }; - - await expect(() => accessTokenClient.acquireAccessToken(requestOpts)).rejects.toThrow( - Error('Cannot pass a code_verifier when flow type is pre-authorized'), - ); - }); - it('get error if no as, issuer and metadata values are present', async () => { await expect(() => AccessTokenClient.determineTokenURL({ diff --git a/packages/client/lib/__tests__/IT.spec.ts b/packages/client/lib/__tests__/IT.spec.ts index 74c3a741..00ca84ea 100644 --- a/packages/client/lib/__tests__/IT.spec.ts +++ b/packages/client/lib/__tests__/IT.spec.ts @@ -1,7 +1,6 @@ import { AccessTokenResponse, Alg, - AuthzFlowType, CredentialOfferRequestWithBaseUrl, Jwt, OpenId4VCIVersion, @@ -72,7 +71,6 @@ describe('OID4VCI-Client should', () => { succeedWithAFullFlowWithClientSetup(); const client = await OpenID4VCIClient.fromURI({ uri: INITIATE_QR, - flowType: AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', alg: Alg.ES256, clientId: 'test-clientId', @@ -84,7 +82,6 @@ describe('OID4VCI-Client should', () => { succeedWithAFullFlowWithClientSetup(); const client = await OpenID4VCIClient.fromURI({ uri: OFFER_QR, - flowType: AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', alg: Alg.ES256, clientId: 'test-clientId', @@ -93,7 +90,6 @@ describe('OID4VCI-Client should', () => { }); async function assertionOfsucceedWithAFullFlowWithClient(client: OpenID4VCIClient) { - expect(client.flowType).toEqual(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW); expect(client.credentialOffer).toBeDefined(); expect(client.endpointMetadata).toBeDefined(); expect(client.getIssuer()).toEqual('https://issuer.research.identiproof.io'); diff --git a/packages/client/lib/__tests__/MattrE2E.spec.test.ts b/packages/client/lib/__tests__/MattrE2E.spec.test.ts index 70f5f5f0..9000831c 100644 --- a/packages/client/lib/__tests__/MattrE2E.spec.test.ts +++ b/packages/client/lib/__tests__/MattrE2E.spec.test.ts @@ -1,4 +1,4 @@ -import { Alg, AuthzFlowType, Jwt } from '@sphereon/oid4vci-common'; +import { Alg, Jwt } from '@sphereon/oid4vci-common'; import { CredentialMapper } from '@sphereon/ssi-types'; import { fetch } from 'cross-fetch'; import { importJWK, JWK, SignJWT } from 'jose'; @@ -25,11 +25,9 @@ describe('OID4VCI-Client using Mattr issuer should', () => { const offer = await getCredentialOffer(format); const client = await OpenID4VCIClient.fromURI({ uri: offer.offerUrl, - flowType: AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW, kid, alg: Alg.EdDSA, }); - expect(client.flowType).toEqual(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW); expect(client.credentialOffer).toBeDefined(); expect(client.endpointMetadata).toBeDefined(); expect(client.getCredentialEndpoint()).toEqual(`${ISSUER_URL}/oidc/v1/auth/credential`); diff --git a/packages/client/lib/__tests__/OpenID4VCIClient.spec.ts b/packages/client/lib/__tests__/OpenID4VCIClient.spec.ts index c3d9da59..4524f30e 100644 --- a/packages/client/lib/__tests__/OpenID4VCIClient.spec.ts +++ b/packages/client/lib/__tests__/OpenID4VCIClient.spec.ts @@ -1,4 +1,4 @@ -import { AuthzFlowType, CodeChallengeMethod, WellKnownEndpoints } from '@sphereon/oid4vci-common'; +import { CodeChallengeMethod, WellKnownEndpoints } from '@sphereon/oid4vci-common'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import nock from 'nock'; @@ -15,8 +15,8 @@ describe('OpenID4VCIClient should', () => { nock(MOCK_URL).get(WellKnownEndpoints.OAUTH_AS).reply(404, {}); nock(MOCK_URL).get(WellKnownEndpoints.OPENID_CONFIGURATION).reply(404, {}); client = await OpenID4VCIClient.fromURI({ + clientId: 'test-client', uri: 'openid-initiate-issuance://?issuer=https://server.example.com&credential_type=TestCredential', - flowType: AuthzFlowType.AUTHORIZATION_CODE_FLOW, }); }); @@ -29,7 +29,6 @@ describe('OpenID4VCIClient should', () => { // @ts-ignore client._endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`; const url = client.createAuthorizationRequestUrl({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', scope: 'openid TestCredential', @@ -44,7 +43,6 @@ describe('OpenID4VCIClient should', () => { it('throw an error if authorization endpoint is not set in server metadata', async () => { expect(() => { client.createAuthorizationRequestUrl({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', scope: 'openid TestCredential', @@ -58,7 +56,6 @@ describe('OpenID4VCIClient should', () => { client._endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`; const url = client.createAuthorizationRequestUrl({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', scope: 'TestCredential', @@ -77,7 +74,6 @@ describe('OpenID4VCIClient should', () => { expect(() => { client.createAuthorizationRequestUrl({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', redirectUri: 'http://localhost:8881/cb', @@ -91,7 +87,6 @@ describe('OpenID4VCIClient should', () => { expect( client.createAuthorizationRequestUrl({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', authorizationDetails: [ @@ -112,7 +107,7 @@ describe('OpenID4VCIClient should', () => { redirectUri: 'http://localhost:8881/cb', }), ).toEqual( - 'https://server.example.com/v1/auth/authorize?response_type=code&client_id=test-client&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D%2C%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22mso_mdoc%22%2C%22doctype%22%3A%22org%2Eiso%2E18013%2E5%2E1%2EmDL%22%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb', + 'https://server.example.com/v1/auth/authorize?response_type=code&client_id=test-client&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D%2C%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22mso_mdoc%22%2C%22doctype%22%3A%22org%2Eiso%2E18013%2E5%2E1%2EmDL%22%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&scope=openid', ); }); it('create an authorization request url with authorization_details object property', async () => { @@ -122,7 +117,6 @@ describe('OpenID4VCIClient should', () => { expect( client.createAuthorizationRequestUrl({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', authorizationDetails: { @@ -136,7 +130,7 @@ describe('OpenID4VCIClient should', () => { redirectUri: 'http://localhost:8881/cb', }), ).toEqual( - 'https://server.example.com/v1/auth/authorize?response_type=code&client_id=test-client&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb', + 'https://server.example.com/v1/auth/authorize?response_type=code&client_id=test-client&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&scope=openid', ); }); it('create an authorization request url with authorization_details and scope', async () => { @@ -146,7 +140,6 @@ describe('OpenID4VCIClient should', () => { expect( client.createAuthorizationRequestUrl({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', authorizationDetails: { diff --git a/packages/client/lib/__tests__/OpenID4VCIClientPAR.spec.ts b/packages/client/lib/__tests__/OpenID4VCIClientPAR.spec.ts index 8a7e8ee6..bad3da3c 100644 --- a/packages/client/lib/__tests__/OpenID4VCIClientPAR.spec.ts +++ b/packages/client/lib/__tests__/OpenID4VCIClientPAR.spec.ts @@ -1,4 +1,4 @@ -import { AuthzFlowType, CodeChallengeMethod, WellKnownEndpoints } from '@sphereon/oid4vci-common'; +import { CodeChallengeMethod, WellKnownEndpoints } from '@sphereon/oid4vci-common'; import nock from 'nock'; import { OpenID4VCIClient } from '../OpenID4VCIClient'; @@ -13,8 +13,8 @@ describe('OpenID4VCIClient', () => { nock(MOCK_URL).get(WellKnownEndpoints.OPENID_CONFIGURATION).reply(404, {}); nock(`${MOCK_URL}`).post('/v1/auth/par').reply(201, { request_uri: 'test_uri', expires_in: 90 }); client = await OpenID4VCIClient.fromURI({ + clientId: 'test-client', uri: 'openid-initiate-issuance://?issuer=https://server.example.com&credential_type=TestCredential', - flowType: AuthzFlowType.AUTHORIZATION_CODE_FLOW, }); }); @@ -24,20 +24,19 @@ describe('OpenID4VCIClient', () => { it('should successfully retrieve the authorization code using PAR', async () => { client.endpointMetadata.credentialIssuerMetadata!.pushed_authorization_request_endpoint = `${MOCK_URL}v1/auth/par`; + client.endpointMetadata.credentialIssuerMetadata!.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`; const actual = await client.acquirePushedAuthorizationRequestURI({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', scope: 'openid TestCredential', redirectUri: 'http://localhost:8881/cb', }); - expect(actual.successBody).toEqual({ request_uri: 'test_uri', expires_in: 90 }); + expect(actual).toEqual('https://server.example.com/v1/auth/authorize?request_uri=test_uri'); }); it('should fail when pushed_authorization_request_endpoint is not present', async () => { await expect(() => client.acquirePushedAuthorizationRequestURI({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', scope: 'openid TestCredential', @@ -49,7 +48,6 @@ describe('OpenID4VCIClient', () => { it('should fail when authorization_details and scope are not present', async () => { await expect(() => client.acquirePushedAuthorizationRequestURI({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', redirectUri: 'http://localhost:8881/cb', @@ -59,8 +57,8 @@ describe('OpenID4VCIClient', () => { it('should not fail when only authorization_details is present', async () => { client.endpointMetadata.credentialIssuerMetadata!.pushed_authorization_request_endpoint = `${MOCK_URL}v1/auth/par`; + client.endpointMetadata.credentialIssuerMetadata!.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`; const actual = await client.acquirePushedAuthorizationRequestURI({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', authorizationDetails: [ @@ -75,25 +73,25 @@ describe('OpenID4VCIClient', () => { ], redirectUri: 'http://localhost:8881/cb', }); - expect(actual.successBody).toEqual({ request_uri: 'test_uri', expires_in: 90 }); + expect(actual).toEqual('https://server.example.com/v1/auth/authorize?request_uri=test_uri'); }); it('should not fail when only scope is present', async () => { client.endpointMetadata.credentialIssuerMetadata!.pushed_authorization_request_endpoint = `${MOCK_URL}v1/auth/par`; + client.endpointMetadata.credentialIssuerMetadata!.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`; const actual = await client.acquirePushedAuthorizationRequestURI({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', scope: 'openid TestCredential', redirectUri: 'http://localhost:8881/cb', }); - expect(actual.successBody).toEqual({ request_uri: 'test_uri', expires_in: 90 }); + expect(actual).toEqual('https://server.example.com/v1/auth/authorize?request_uri=test_uri'); }); it('should not fail when both authorization_details and scope are present', async () => { client.endpointMetadata.credentialIssuerMetadata!.pushed_authorization_request_endpoint = `${MOCK_URL}v1/auth/par`; + client.endpointMetadata.credentialIssuerMetadata!.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`; const actual = await client.acquirePushedAuthorizationRequestURI({ - clientId: 'test-client', codeChallengeMethod: CodeChallengeMethod.SHA256, codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs', authorizationDetails: [ @@ -109,6 +107,6 @@ describe('OpenID4VCIClient', () => { scope: 'openid TestCredential', redirectUri: 'http://localhost:8881/cb', }); - expect(actual.successBody).toEqual({ request_uri: 'test_uri', expires_in: 90 }); + expect(actual).toEqual('https://server.example.com/v1/auth/authorize?request_uri=test_uri'); }); }); diff --git a/packages/common/lib/functions/Encoding.ts b/packages/common/lib/functions/Encoding.ts index 3e789765..b84a918a 100644 --- a/packages/common/lib/functions/Encoding.ts +++ b/packages/common/lib/functions/Encoding.ts @@ -1,4 +1,4 @@ -import { BAD_PARAMS, DecodeURIAsJsonOpts, EncodeJsonAsURIOpts, SearchValue } from '../types'; +import { BAD_PARAMS, DecodeURIAsJsonOpts, EncodeJsonAsURIOpts, OpenId4VCIVersion, SearchValue } from '../types'; /** * @function encodeJsonAsURI encodes a Json object into a URI @@ -29,35 +29,39 @@ export function convertJsonToURI( return encodeURIComponent(key.replace(' ', '')); } - for (const [key, value] of Object.entries(json)) { - if (!value) { - continue; - } - //Skip properties that are not of URL type - if (!opts?.uriTypeProperties?.includes(key)) { - results.push(`${key}=${value}`); - continue; - } - if (opts?.arrayTypeProperties?.includes(key) && Array.isArray(value)) { - results.push(value.map((v) => `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(v, /\./g)}`).join('&')); - continue; - } - const isBool = typeof value == 'boolean'; - const isNumber = typeof value == 'number'; - const isString = typeof value == 'string'; - let encoded; - if (isBool || isNumber) { - encoded = `${encodeAndStripWhitespace(key)}=${value}`; - } else if (isString) { - encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(value, /\./g)}`; - } else { - encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(JSON.stringify(value), /\./g)}`; + let components: string; + if (opts?.version && opts.version > OpenId4VCIVersion.VER_1_0_08) { + // v11 changed from encoding every param to a encoded json object with a credential_offer param key + components = encodeAndStripWhitespace(JSON.stringify(json)); + } else { + for (const [key, value] of Object.entries(json)) { + if (!value) { + continue; + } + //Skip properties that are not of URL type + if (!opts?.uriTypeProperties?.includes(key)) { + results.push(`${key}=${value}`); + continue; + } + if (opts?.arrayTypeProperties?.includes(key) && Array.isArray(value)) { + results.push(value.map((v) => `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(v, /\./g)}`).join('&')); + continue; + } + const isBool = typeof value == 'boolean'; + const isNumber = typeof value == 'number'; + const isString = typeof value == 'string'; + let encoded; + if (isBool || isNumber) { + encoded = `${encodeAndStripWhitespace(key)}=${value}`; + } else if (isString) { + encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(value, /\./g)}`; + } else { + encoded = `${encodeAndStripWhitespace(key)}=${customEncodeURIComponent(JSON.stringify(value), /\./g)}`; + } + results.push(encoded); } - results.push(encoded); + components = results.join('&'); } - - const components = results.join('&'); - if (opts?.baseUrl) { if (opts.baseUrl.endsWith('=')) { if (opts.param) { diff --git a/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts b/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts index 6364a7b6..c19316be 100644 --- a/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts +++ b/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts @@ -5,7 +5,6 @@ import { OpenID4VCIClient } from '@sphereon/oid4vci-client' import { AccessTokenResponse, Alg, - AuthzFlowType, CredentialOfferSession, CredentialSupported, IssuerCredentialSubjectDisplay, @@ -207,7 +206,6 @@ describe('VcIssuer', () => { it('should create client from credential offer URI', async () => { client = await OpenID4VCIClient.fromURI({ uri, - flowType: AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW, kid: subjectDIDKey.didDocument.authentication[0], alg: 'ES256', }) diff --git a/packages/issuer/lib/__tests__/VcIssuer.spec.ts b/packages/issuer/lib/__tests__/VcIssuer.spec.ts index 4ff68ce6..dd5d9e97 100644 --- a/packages/issuer/lib/__tests__/VcIssuer.spec.ts +++ b/packages/issuer/lib/__tests__/VcIssuer.spec.ts @@ -1,7 +1,6 @@ import { OpenID4VCIClient } from '@sphereon/oid4vci-client' import { Alg, - AuthzFlowType, CredentialOfferLdpVcV1_0_11, CredentialOfferSession, CredentialSupported, @@ -153,7 +152,7 @@ describe('VcIssuer', () => { 'http://issuer-example.com?credential_offer=%7B%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22previously-created-state%22%7D%2C%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22test_code%22%2C%22user_pin_required%22%3Atrue%7D%7D%2C%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%5D%2C%22credentialSubject%22%3A%7B%22given_name%22%3A%7B%22name%22%3A%22given%20name%22%2C%22locale%22%3A%22en-US%22%7D%7D%2C%22cryptographic_suites_supported%22%3A%5B%22ES256K%22%5D%2C%22cryptographic_binding_methods_supported%22%3A%5B%22did%22%5D%2C%22id%22%3A%22UniversityDegree_JWT%22%2C%22display%22%3A%5B%7B%22name%22%3A%22University%20Credential%22%2C%22locale%22%3A%22en-US%22%2C%22logo%22%3A%7B%22url%22%3A%22https%3A%2F%2Fexampleuniversity.com%2Fpublic%2Flogo.png%22%2C%22alt_text%22%3A%22a%20square%20logo%20of%20a%20university%22%7D%2C%22background_color%22%3A%22%2312107c%22%2C%22text_color%22%3A%22%23FFFFFF%22%7D%5D%7D%5D%7D', ) - const client = await OpenID4VCIClient.fromURI({ uri, flowType: AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW }) + const client = await OpenID4VCIClient.fromURI({ uri }) expect(client.credentialOffer).toEqual({ baseUrl: 'http://issuer-example.com', credential_offer: { From a73afee4c93905a9e33dff9dd9e4f798646d8d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linas=20Is=CC=8Cganaitis?= Date: Sat, 7 Oct 2023 23:14:23 +0200 Subject: [PATCH 3/3] Add client_id only if it was provided during client initialization --- packages/client/lib/OpenID4VCIClient.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/client/lib/OpenID4VCIClient.ts b/packages/client/lib/OpenID4VCIClient.ts index fe90709a..53aa2eaa 100644 --- a/packages/client/lib/OpenID4VCIClient.ts +++ b/packages/client/lib/OpenID4VCIClient.ts @@ -116,7 +116,6 @@ export class OpenID4VCIClient { const queryObj: { [key: string]: string } = { response_type: ResponseType.AUTH_CODE, - client_id: this.clientId || '', code_challenge_method: codeChallengeMethod, code_challenge: codeChallenge, authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)), @@ -124,6 +123,10 @@ export class OpenID4VCIClient { scope: scope, }; + if (this.clientId) { + queryObj['client_id'] = this.clientId; + } + if (this.credentialOffer.issuerState) { queryObj['issuer_state'] = this.credentialOffer.issuerState; } @@ -167,7 +170,6 @@ export class OpenID4VCIClient { const queryObj: { [key: string]: string } = { response_type: ResponseType.AUTH_CODE, - client_id: this.clientId || '', code_challenge_method: codeChallengeMethod, code_challenge: codeChallenge, authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)), @@ -175,6 +177,10 @@ export class OpenID4VCIClient { scope: scope, }; + if (this.clientId) { + queryObj['client_id'] = this.clientId; + } + if (this.credentialOffer.issuerState) { queryObj['issuer_state'] = this.credentialOffer.issuerState; }