Skip to content

Commit

Permalink
feat: Expose DPoP support also to main clients, instead of only to th…
Browse files Browse the repository at this point in the history
…e access token client and credential request client
  • Loading branch information
nklomp committed Aug 11, 2024
1 parent ac29c5d commit e2cc7f6
Show file tree
Hide file tree
Showing 14 changed files with 3,360 additions and 3,340 deletions.
2 changes: 1 addition & 1 deletion packages/callback-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@sphereon/oid4vci-client": "workspace:*",
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/oid4vci-issuer": "workspace:*",
"@sphereon/ssi-types": "0.28.0",
"@sphereon/ssi-types": "0.29.0",
"jose": "^4.10.0"
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/client/lib/AccessTokenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export class AccessTokenClient {

return {
...response,
params: { ...(nextDPoPNonce && { dpop: { dpopNonce: nextDPoPNonce } }) },
...(nextDPoPNonce && { params: { dpop: { dpopNonce: nextDPoPNonce } } }),
};
}

Expand Down Expand Up @@ -238,6 +238,7 @@ export class AccessTokenClient {
throw new Error('Authorization flow requires the code to be present');
}
}

private validate(accessTokenRequest: AccessTokenRequest, pinMeta?: TxCodeAndPinRequired): void {
if (accessTokenRequest.grant_type === GrantTypes.PRE_AUTHORIZED_CODE) {
this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type);
Expand Down
3 changes: 2 additions & 1 deletion packages/client/lib/AccessTokenClientV1_0_11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class AccessTokenClientV1_0_11 {

return {
...response,
params: { ...(nextDPoPNonce && { dpop: { dpopNonce: nextDPoPNonce } }) },
...(nextDPoPNonce && { params: { dpop: { dpopNonce: nextDPoPNonce } } }),
};
}

Expand Down Expand Up @@ -220,6 +220,7 @@ export class AccessTokenClientV1_0_11 {
throw new Error('Authorization flow requires the code to be present');
}
}

private validate(accessTokenRequest: AccessTokenRequest, isPinRequired?: boolean): void {
if (accessTokenRequest.grant_type === GrantTypes.PRE_AUTHORIZED_CODE) {
this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type);
Expand Down
6 changes: 3 additions & 3 deletions packages/client/lib/CredentialRequestClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export class CredentialRequestClient {

let response = (await post(credentialEndpoint, JSON.stringify(request), {
bearerToken: requestToken,
customHeaders: { ...(dPoP && { dpop: dPoP }) },
...(dPoP && { customHeaders: { dpop: dPoP } }),
})) as OpenIDResponse<CredentialResponse> & {
access_token: string;
};
Expand All @@ -142,7 +142,7 @@ export class CredentialRequestClient {

response = (await post(credentialEndpoint, JSON.stringify(request), {
bearerToken: requestToken,
customHeaders: { ...(createDPoPOpts && { dpop: dPoP }) },
...(createDPoPOpts && { customHeaders: { dpop: dPoP } }),
})) as OpenIDResponse<CredentialResponse> & {
access_token: string;
};
Expand All @@ -166,7 +166,7 @@ export class CredentialRequestClient {

return {
...response,
params: { ...(nextDPoPNonce && { dpop: { dpopNonce: nextDPoPNonce } }) },
...(nextDPoPNonce && { params: { dpop: { dpopNonce: nextDPoPNonce } } }),
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/client/lib/CredentialRequestClientV1_0_11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export class CredentialRequestClientV1_0_11 {

return {
...response,
params: { ...(nextDPoPNonce && { dpop: { dpopNonce: nextDPoPNonce } }) },
...(nextDPoPNonce && { params: { dpop: { dpopNonce: nextDPoPNonce } } }),
};
}

Expand Down
37 changes: 23 additions & 14 deletions packages/client/lib/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { JWK } from '@sphereon/oid4vc-common';
import { CreateDPoPClientOpts, JWK } from '@sphereon/oid4vc-common';
import {
AccessTokenRequestOpts,
AccessTokenResponse,
Alg,
AuthorizationRequestOpts,
Expand All @@ -16,6 +17,7 @@ import {
CredentialsSupportedLegacy,
DefaultURISchemes,
determineVersionsFromIssuerMetadata,
DPoPResponseParams,
EndpointMetadataResultV1_0_11,
EndpointMetadataResultV1_0_13,
ExperimentalSubjectIssuance,
Expand Down Expand Up @@ -268,16 +270,13 @@ export class OpenID4VCIClient {
this._state.pkce = generateMissingPKCEOpts({ ...this._state.pkce, ...pkce });
}

public async acquireAccessToken(opts?: {
pin?: string;
clientId?: string;
codeVerifier?: string;
authorizationResponse?: string | AuthorizationResponse; // Pass in an auth response, either as URI/redirect, or object
code?: string; // Directly pass in a code from an auth response
redirectUri?: string;
additionalRequestParams?: Record<string, any>;
asOpts?: AuthorizationServerOpts;
}): Promise<AccessTokenResponse> {
public async acquireAccessToken(
opts?: Omit<AccessTokenRequestOpts, 'credentialOffer' | 'credentialIssuer' | 'metadata' | 'additionalParams'> & {
clientId?: string;
authorizationResponse?: string | AuthorizationResponse; // Pass in an auth response, either as URI/redirect, or object
additionalRequestParams?: Record<string, any>;
},
): Promise<AccessTokenResponse & DPoPResponseParams> {
const { pin, clientId = this._state.clientId ?? this._state.authorizationRequestOpts?.clientId } = opts ?? {};
let { redirectUri } = opts ?? {};
if (opts?.authorizationResponse) {
Expand Down Expand Up @@ -336,6 +335,7 @@ export class OpenID4VCIClient {
code,
redirectUri,
asOpts,
...(opts?.createDPoPOpts && { createDPoPOpts: opts.createDPoPOpts }),
...(opts?.additionalRequestParams && { additionalParams: opts.additionalRequestParams }),
});

Expand All @@ -355,10 +355,11 @@ export class OpenID4VCIClient {
);
}
this._state.accessTokenResponse = response.successBody;
this._state.dpopResponseParams = response.params;
this._state.accessToken = response.successBody.access_token;
}

return this.accessTokenResponse;
return { ...this.accessTokenResponse, ...(this.dpopResponseParams && { params: this.dpopResponseParams }) };
}

public async acquireCredentials({
Expand All @@ -372,6 +373,7 @@ export class OpenID4VCIClient {
jti,
deferredCredentialAwait,
deferredCredentialIntervalInMS,
createDPoPOpts,
}: {
credentialTypes: string | string[];
context?: string[];
Expand All @@ -384,7 +386,8 @@ export class OpenID4VCIClient {
deferredCredentialAwait?: boolean;
deferredCredentialIntervalInMS?: number;
experimentalHolderIssuanceSupported?: boolean;
}): Promise<CredentialResponse & { access_token: string }> {
createDPoPOpts?: CreateDPoPClientOpts;
}): Promise<CredentialResponse & { params?: DPoPResponseParams; access_token: string }> {
if ([jwk, kid].filter((v) => v !== undefined).length > 1) {
throw new Error(KID_JWK_X5C_ERROR + `. jwk: ${jwk !== undefined}, kid: ${kid !== undefined}`);
}
Expand Down Expand Up @@ -487,7 +490,9 @@ export class OpenID4VCIClient {
context,
format,
subjectIssuance,
createDPoPOpts,
});
this._state.dpopResponseParams = response.params;
if (response.errorBody) {
debug(`Credential request error:\r\n${JSON.stringify(response.errorBody)}`);
throw Error(
Expand All @@ -503,7 +508,7 @@ export class OpenID4VCIClient {
} for issuer ${this.getIssuer()} failed as there was no success response body`,
);
}
return { ...response.successBody, access_token: response.access_token };
return { ...response.successBody, ...(this.dpopResponseParams && { params: this.dpopResponseParams }), access_token: response.access_token };
}

public async exportState(): Promise<string> {
Expand Down Expand Up @@ -625,6 +630,10 @@ export class OpenID4VCIClient {
return this._state.accessTokenResponse!;
}

get dpopResponseParams(): DPoPResponseParams | undefined {
return this._state.dpopResponseParams;
}

public getIssuer(): string {
this.assertIssuerData();
return this._state.credentialIssuer;
Expand Down
39 changes: 25 additions & 14 deletions packages/client/lib/OpenID4VCIClientV1_0_11.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { JWK } from '@sphereon/oid4vc-common';
import { CreateDPoPClientOpts, JWK } from '@sphereon/oid4vc-common';
import {
AccessTokenRequestOpts,
AccessTokenResponse,
Alg,
AuthorizationRequestOpts,
Expand All @@ -14,6 +15,7 @@ import {
CredentialResponse,
CredentialsSupportedLegacy,
DefaultURISchemes,
DPoPResponseParams,
EndpointMetadataResultV1_0_11,
getClientIdFromCredentialOfferPayload,
getIssuerFromCredentialOfferPayload,
Expand All @@ -36,7 +38,7 @@ import { CredentialOfferClientV1_0_11 } from './CredentialOfferClientV1_0_11';
import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
import { MetadataClientV1_0_11 } from './MetadataClientV1_0_11';
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
import { generateMissingPKCEOpts } from './functions/AuthorizationUtil';
import { generateMissingPKCEOpts } from './functions';

const debug = Debug('sphereon:oid4vci');

Expand All @@ -49,6 +51,7 @@ export interface OpenID4VCIClientStateV1_0_11 {
alg?: Alg | string;
endpointMetadata?: EndpointMetadataResultV1_0_11;
accessTokenResponse?: AccessTokenResponse;
dpopResponseParams?: DPoPResponseParams;
authorizationRequestOpts?: AuthorizationRequestOpts;
authorizationCodeResponse?: AuthorizationResponse;
pkce: PKCEOpts;
Expand Down Expand Up @@ -253,16 +256,13 @@ export class OpenID4VCIClientV1_0_11 {
this._state.pkce = generateMissingPKCEOpts({ ...this._state.pkce, ...pkce });
}

public async acquireAccessToken(opts?: {
pin?: string;
clientId?: string;
codeVerifier?: string;
authorizationResponse?: string | AuthorizationResponse; // Pass in an auth response, either as URI/redirect, or object
code?: string; // Directly pass in a code from an auth response
redirectUri?: string;
additionalRequestParams?: Record<string, any>;
asOpts?: AuthorizationServerOpts;
}): Promise<AccessTokenResponse> {
public async acquireAccessToken(
opts?: Omit<AccessTokenRequestOpts, 'credentialOffer' | 'credentialIssuer' | 'metadata' | 'additionalParams'> & {
clientId?: string;
authorizationResponse?: string | AuthorizationResponse; // Pass in an auth response, either as URI/redirect, or object
additionalRequestParams?: Record<string, any>;
},
): Promise<AccessTokenResponse> {
const { pin, clientId = this._state.clientId ?? this._state.authorizationRequestOpts?.clientId } = opts ?? {};
let { redirectUri } = opts ?? {};
if (opts?.authorizationResponse) {
Expand Down Expand Up @@ -320,6 +320,7 @@ export class OpenID4VCIClientV1_0_11 {
code,
redirectUri,
asOpts,
...(opts?.createDPoPOpts && { createDPoPOpts: opts.createDPoPOpts }),
...(opts?.additionalRequestParams && { additionalParams: opts.additionalRequestParams }),
});

Expand All @@ -339,9 +340,11 @@ export class OpenID4VCIClientV1_0_11 {
);
}
this._state.accessTokenResponse = response.successBody;
this._state.dpopResponseParams = response.params;
this._state.accessToken = response.successBody.access_token;
}

return this.accessTokenResponse;
return { ...this.accessTokenResponse, ...(this.dpopResponseParams && { params: this.dpopResponseParams }) };
}

public async acquireCredentials({
Expand All @@ -355,6 +358,7 @@ export class OpenID4VCIClientV1_0_11 {
jti,
deferredCredentialAwait,
deferredCredentialIntervalInMS,
createDPoPOpts,
}: {
credentialTypes: string | string[];
context?: string[];
Expand All @@ -366,6 +370,7 @@ export class OpenID4VCIClientV1_0_11 {
jti?: string;
deferredCredentialAwait?: boolean;
deferredCredentialIntervalInMS?: number;
createDPoPOpts?: CreateDPoPClientOpts;
}): Promise<CredentialResponse> {
if ([jwk, kid].filter((v) => v !== undefined).length > 1) {
throw new Error(KID_JWK_X5C_ERROR + `. jwk: ${jwk !== undefined}, kid: ${kid !== undefined}`);
Expand Down Expand Up @@ -445,7 +450,9 @@ export class OpenID4VCIClientV1_0_11 {
credentialTypes,
context,
format,
createDPoPOpts,
});
this._state.dpopResponseParams = response.params;
if (response.errorBody) {
debug(`Credential request error:\r\n${JSON.stringify(response.errorBody)}`);
throw Error(
Expand All @@ -461,7 +468,7 @@ export class OpenID4VCIClientV1_0_11 {
} for issuer ${this.getIssuer()} failed as there was no success response body`,
);
}
return response.successBody;
return { ...response.successBody, ...(this.dpopResponseParams && { params: this.dpopResponseParams }) };
}

public async exportState(): Promise<string> {
Expand Down Expand Up @@ -576,6 +583,10 @@ export class OpenID4VCIClientV1_0_11 {
return this._state.accessTokenResponse!;
}

get dpopResponseParams(): DPoPResponseParams | undefined {
return this._state.dpopResponseParams;
}

public getIssuer(): string {
this.assertIssuerData();
return this._state.credentialIssuer;
Expand Down
Loading

0 comments on commit e2cc7f6

Please sign in to comment.