Skip to content

Commit 86d0423

Browse files
committed
Merge branch 'develop' of github.com:Sphereon-Opensource/OID4VCI into develop
2 parents c7c6af4 + 858a8ea commit 86d0423

7 files changed

Lines changed: 23 additions & 61 deletions

File tree

packages/client/lib/AccessTokenClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export class AccessTokenClient {
135135
if (credentialOfferRequest?.supportedFlows.includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
136136
this.assertAlphanumericPin(opts.pinMetadata, pin);
137137
request.user_pin = pin;
138+
request.tx_code = pin;
138139

139140
request.grant_type = GrantTypes.PRE_AUTHORIZED_CODE;
140141
// we actually know it is there because of the isPreAuthCode call

packages/client/lib/AuthorizationCodeClient.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,16 @@ export const createAuthorizationRequestUrl = async ({
113113
const { redirectUri, requestObjectOpts = { requestObjectMode: CreateRequestObjectMode.NONE } } = authorizationRequest;
114114
const client_id = clientId ?? authorizationRequest.clientId;
115115

116-
let { scope, authorizationDetails } = authorizationRequest;
117-
const parMode = endpointMetadata?.credentialIssuerMetadata?.require_pushed_authorization_requests
116+
// Authorization server metadata takes precedence
117+
const authorizationMetadata = endpointMetadata.authorizationServerMetadata ?? endpointMetadata.credentialIssuerMetadata
118+
119+
let { authorizationDetails } = authorizationRequest;
120+
const parMode = authorizationMetadata?.require_pushed_authorization_requests
118121
? PARMode.REQUIRE
119122
: (authorizationRequest.parMode ?? (client_id ? PARMode.AUTO : PARMode.NEVER));
120123
// Scope and authorization_details can be used in the same authorization request
121124
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
122-
if (!scope && !authorizationDetails) {
125+
if (!authorizationRequest.scope && !authorizationDetails) {
123126
if (!credentialOffer) {
124127
throw Error('Please provide a scope or authorization_details if no credential offer is present');
125128
}
@@ -177,12 +180,8 @@ export const createAuthorizationRequestUrl = async ({
177180
if (!endpointMetadata?.authorization_endpoint) {
178181
throw Error('Server metadata does not contain authorization endpoint');
179182
}
180-
const parEndpoint = endpointMetadata.credentialIssuerMetadata?.pushed_authorization_request_endpoint;
183+
const parEndpoint = authorizationMetadata?.pushed_authorization_request_endpoint;
181184

182-
// add 'openid' scope if not present
183-
if (!scope?.includes('openid')) {
184-
scope = ['openid', scope].filter((s) => !!s).join(' ');
185-
}
186185

187186
let queryObj: Record<string, any> | PushedAuthorizationResponse = {
188187
response_type: ResponseType.AUTH_CODE,
@@ -194,7 +193,7 @@ export const createAuthorizationRequestUrl = async ({
194193
...(redirectUri && { redirect_uri: redirectUri }),
195194
...(client_id && { client_id }),
196195
...(credentialOffer?.issuerState && { issuer_state: credentialOffer.issuerState }),
197-
scope,
196+
scope: authorizationRequest.scope,
198197
};
199198

200199
if (!parEndpoint && parMode === PARMode.REQUIRE) {
@@ -210,11 +209,11 @@ export const createAuthorizationRequestUrl = async ({
210209
{ contentType: 'application/x-www-form-urlencoded', accept: 'application/json' },
211210
);
212211
if (parResponse.errorBody || !parResponse.successBody) {
213-
console.log(JSON.stringify(parResponse.errorBody));
214-
console.log('Falling back to regular request URI, since PAR failed');
215212
if (parMode === PARMode.REQUIRE) {
216213
throw Error(`PAR error: ${parResponse.origResponse.statusText}`);
217214
}
215+
216+
debug('Falling back to regular request URI, since PAR failed', JSON.stringify(parResponse.errorBody));
218217
} else {
219218
debug(`PAR response: ${JSON.stringify(parResponse.successBody, null, 2)}`);
220219
queryObj = { /*response_type: ResponseType.AUTH_CODE,*/ client_id, request_uri: parResponse.successBody.request_uri };

packages/client/lib/__tests__/OpenID4VCIClient.spec.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,27 +59,6 @@ describe('OpenID4VCIClient should', () => {
5959
}),
6060
).rejects.toThrow(Error('Server metadata does not contain authorization endpoint'));
6161
});
62-
it("injects 'openid' as the first scope if not provided", async () => {
63-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
64-
// @ts-ignore
65-
client._state.endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
66-
67-
const url = await client.createAuthorizationRequestUrl({
68-
pkce: {
69-
codeChallengeMethod: CodeChallengeMethod.S256,
70-
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
71-
},
72-
authorizationRequest: {
73-
scope: 'TestCredential',
74-
redirectUri: 'http://localhost:8881/cb',
75-
},
76-
});
77-
78-
const urlSearchParams = new URLSearchParams(url.split('?')[1]);
79-
const scope = urlSearchParams.get('scope')?.split(' ');
80-
81-
expect(scope?.[0]).toBe('openid');
82-
});
8362
it('throw an error if no scope and no authorization_details is provided', async () => {
8463
nock(MOCK_URL).get(/.*/).reply(200, {});
8564
nock(MOCK_URL).get(WellKnownEndpoints.OAUTH_AS).reply(200, {});
@@ -149,7 +128,7 @@ describe('OpenID4VCIClient should', () => {
149128
},
150129
}),
151130
).resolves.toEqual(
152-
'https://server.example.com/v1/auth/authorize?response_type=code&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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
131+
'https://server.example.com/v1/auth/authorize?response_type=code&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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client',
153132
);
154133
});
155134
it('create an authorization request url with authorization_details object property', async () => {
@@ -176,7 +155,7 @@ describe('OpenID4VCIClient should', () => {
176155
},
177156
}),
178157
).resolves.toEqual(
179-
'https://server.example.com/v1/auth/authorize?response_type=code&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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
158+
'https://server.example.com/v1/auth/authorize?response_type=code&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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client',
180159
);
181160
});
182161

packages/client/lib/__tests__/OpenID4VCIClientV1_0_13.spec.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -51,27 +51,6 @@ describe('OpenID4VCIClientV1_0_13 should', () => {
5151
}),
5252
).rejects.toThrow(Error('Server metadata does not contain authorization endpoint'));
5353
});
54-
it("injects 'openid' as the first scope if not provided", async () => {
55-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
56-
// @ts-ignore
57-
client._state.endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
58-
59-
const url = await client.createAuthorizationRequestUrl({
60-
pkce: {
61-
codeChallengeMethod: CodeChallengeMethod.S256,
62-
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
63-
},
64-
authorizationRequest: {
65-
scope: 'TestCredential',
66-
redirectUri: 'http://localhost:8881/cb',
67-
},
68-
});
69-
70-
const urlSearchParams = new URLSearchParams(url.split('?')[1]);
71-
const scope = urlSearchParams.get('scope')?.split(' ');
72-
73-
expect(scope?.[0]).toBe('openid');
74-
});
7554
it('throw an error if no scope and no authorization_details is provided', async () => {
7655
nock(MOCK_URL).get(/.*/).reply(200, {});
7756
nock(MOCK_URL).get(WellKnownEndpoints.OAUTH_AS).reply(200, {});
@@ -141,7 +120,7 @@ describe('OpenID4VCIClientV1_0_13 should', () => {
141120
},
142121
}),
143122
).resolves.toEqual(
144-
'https://server.example.com/v1/auth/authorize?response_type=code&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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
123+
'https://server.example.com/v1/auth/authorize?response_type=code&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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client',
145124
);
146125
});
147126
it('create an authorization request url with authorization_details object property', async () => {
@@ -168,7 +147,7 @@ describe('OpenID4VCIClientV1_0_13 should', () => {
168147
},
169148
}),
170149
).resolves.toEqual(
171-
'https://server.example.com/v1/auth/authorize?response_type=code&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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client&scope=openid',
150+
'https://server.example.com/v1/auth/authorize?response_type=code&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%5B%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&client_id=test-client',
172151
);
173152
});
174153

packages/issuer/lib/VcIssuer.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ export class VcIssuer<DIDDoc extends object> {
192192
status,
193193
notification_id: uuidv4(),
194194
...(userPin && { txCode: userPin }), // We used to use userPin according to older specs. We map these onto txCode now. If both are used, txCode in the end wins, even if they are different
195-
...(txCode && { txCode }),
196195
...(opts.credentialDataSupplierInput && { credentialDataSupplierInput: opts.credentialDataSupplierInput }),
197196
credentialOffer,
198197
}

packages/issuer/lib/tokens/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,17 @@ export const assertValidAccessTokenRequest = async (
109109
invalid_request:
110110
the Authorization Server does not expect a PIN in the pre-authorized flow but the client provides a PIN
111111
*/
112-
if (!credentialOfferSession.credentialOffer.credential_offer?.grants?.[GrantTypes.PRE_AUTHORIZED_CODE]?.tx_code && request.tx_code) {
112+
if (
113+
!credentialOfferSession.credentialOffer.credential_offer?.grants?.[GrantTypes.PRE_AUTHORIZED_CODE]?.tx_code &&
114+
request.tx_code &&
115+
!request.user_pin
116+
) {
113117
// >= v13
114118
throw new TokenError(400, TokenErrorResponse.invalid_request, USER_PIN_NOT_REQUIRED_ERROR)
115119
} else if (
116120
!credentialOfferSession.credentialOffer.credential_offer?.grants?.[GrantTypes.PRE_AUTHORIZED_CODE]?.user_pin_required &&
117-
request.user_pin
121+
request.user_pin &&
122+
!request.tx_code
118123
) {
119124
// <= v12
120125
throw new TokenError(400, TokenErrorResponse.invalid_request, USER_PIN_NOT_REQUIRED_ERROR)

packages/oid4vci-common/lib/types/Authorization.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ export interface AccessTokenRequest {
319319
'pre-authorized_code': string;
320320
redirect_uri?: string;
321321
scope?: string;
322-
user_pin?: string; //pre draft 13
322+
user_pin?: string; //this is for v11, not required in v13 anymore
323323
tx_code?: string; //draft 13
324324
[s: string]: unknown;
325325
}

0 commit comments

Comments
 (0)