-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: dpop support #131
feat: dpop support #131
Changes from 4 commits
162f3e9
981e262
9202667
5034468
f30475a
9efbf32
d5b4b75
01f6d4d
57c7592
3c71599
1a54e69
c24a898
d89ac4f
f92b2b9
668c53f
a102854
b696dba
c7c6af4
86d0423
4c8319e
f25b7d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,8 @@ import { | |
AuthorizationServerOpts, | ||
AuthzFlowType, | ||
convertJsonToURI, | ||
createDPoP, | ||
CreateDPoPClientOptions, | ||
CredentialOfferV1_0_11, | ||
CredentialOfferV1_0_13, | ||
EndpointMetadata, | ||
|
@@ -32,7 +34,7 @@ const debug = Debug('sphereon:oid4vci:token'); | |
|
||
export class AccessTokenClientV1_0_11 { | ||
public async acquireAccessToken(opts: AccessTokenRequestOpts): Promise<OpenIDResponse<AccessTokenResponse>> { | ||
const { asOpts, pin, codeVerifier, code, redirectUri, metadata } = opts; | ||
const { asOpts, pin, codeVerifier, code, redirectUri, metadata, createDPoPOptions } = opts; | ||
|
||
const credentialOffer = opts.credentialOffer ? await assertedUniformCredentialOffer(opts.credentialOffer) : undefined; | ||
const isPinRequired = credentialOffer && this.isPinRequiredValue(credentialOffer.credential_offer); | ||
|
@@ -63,6 +65,7 @@ export class AccessTokenClientV1_0_11 { | |
metadata, | ||
asOpts, | ||
issuerOpts, | ||
createDPoPOptions, | ||
}); | ||
} | ||
|
||
|
@@ -71,13 +74,15 @@ export class AccessTokenClientV1_0_11 { | |
isPinRequired, | ||
metadata, | ||
asOpts, | ||
createDPoPOptions, | ||
issuerOpts, | ||
}: { | ||
accessTokenRequest: AccessTokenRequest; | ||
isPinRequired?: boolean; | ||
metadata?: EndpointMetadata; | ||
asOpts?: AuthorizationServerOpts; | ||
issuerOpts?: IssuerOpts; | ||
createDPoPOptions?: CreateDPoPClientOptions; | ||
nklomp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}): Promise<OpenIDResponse<AccessTokenResponse>> { | ||
this.validate(accessTokenRequest, isPinRequired); | ||
|
||
|
@@ -91,10 +96,18 @@ export class AccessTokenClientV1_0_11 { | |
: undefined, | ||
}); | ||
|
||
return this.sendAuthCode(requestTokenURL, accessTokenRequest); | ||
let dPoP: string | undefined; | ||
if (createDPoPOptions?.dPoPSigningAlgValuesSupported && createDPoPOptions.dPoPSigningAlgValuesSupported.length > 0) { | ||
const htu = requestTokenURL.split('?')[0].split('#')[0]; | ||
nklomp marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we extract this into an util? |
||
dPoP = createDPoPOptions | ||
? await createDPoP({ ...createDPoPOptions, jwtPayloadProps: { ...createDPoPOptions.jwtPayloadProps, htu, htm: 'POST' } }) | ||
: undefined; | ||
} | ||
|
||
return this.sendAuthCode(requestTokenURL, accessTokenRequest, { dPoP }); | ||
} | ||
|
||
public async createAccessTokenRequest(opts: AccessTokenRequestOpts): Promise<AccessTokenRequest> { | ||
public async createAccessTokenRequest(opts: Omit<AccessTokenRequestOpts, 'createDPoPOptions'>): Promise<AccessTokenRequest> { | ||
const { asOpts, pin, codeVerifier, code, redirectUri } = opts; | ||
const credentialOfferRequest = opts.credentialOffer | ||
? await toUniformCredentialOfferRequest(opts.credentialOffer as CredentialOfferV1_0_11 | CredentialOfferV1_0_13) | ||
|
@@ -204,8 +217,14 @@ export class AccessTokenClientV1_0_11 { | |
} | ||
} | ||
|
||
private async sendAuthCode(requestTokenURL: string, accessTokenRequest: AccessTokenRequest): Promise<OpenIDResponse<AccessTokenResponse>> { | ||
return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED })); | ||
private async sendAuthCode( | ||
requestTokenURL: string, | ||
accessTokenRequest: AccessTokenRequest, | ||
options?: { dPoP?: string }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we allow passing customHeaders here as well? So if other extensions are needed we can add it over time without needing to add them? Not sure just thinking here |
||
): Promise<OpenIDResponse<AccessTokenResponse>> { | ||
return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, { mode: JsonURIMode.X_FORM_WWW_URLENCODED }), { | ||
customHeaders: { ...(options?.dPoP && { dpop: options.dPoP }) }, | ||
}); | ||
} | ||
|
||
public static determineTokenURL({ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
import { | ||
acquireDeferredCredential, | ||
createDPoP, | ||
CreateDPoPClientOptions, | ||
CredentialRequestV1_0_13, | ||
CredentialResponse, | ||
getCredentialRequestForVersion, | ||
|
@@ -89,6 +91,7 @@ export class CredentialRequestClient { | |
context?: string[]; | ||
format?: CredentialFormat | OID4VCICredentialFormat; | ||
subjectIssuance?: ExperimentalSubjectIssuance; | ||
createDPoPOptions?: CreateDPoPClientOptions; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As this is inside an opts object, would just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO not the dpop to me is the proof itself not to options for creating it. |
||
}): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> { | ||
const { credentialIdentifier, credentialTypes, proofInput, format, context, subjectIssuance } = opts; | ||
|
||
|
@@ -101,11 +104,12 @@ export class CredentialRequestClient { | |
credentialIdentifier, | ||
subjectIssuance, | ||
}); | ||
return await this.acquireCredentialsUsingRequest(request); | ||
return await this.acquireCredentialsUsingRequest(request, opts.createDPoPOptions); | ||
} | ||
|
||
public async acquireCredentialsUsingRequest( | ||
uniformRequest: UniformCredentialRequest, | ||
createDPoPOptions?: CreateDPoPClientOptions, | ||
): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> { | ||
if (this.version() < OpenId4VCIVersion.VER_1_0_13) { | ||
throw new Error('Versions below v1.0.13 (draft 13) are not supported by the V13 credential request client.'); | ||
|
@@ -119,7 +123,22 @@ export class CredentialRequestClient { | |
debug(`Acquiring credential(s) from: ${credentialEndpoint}`); | ||
debug(`request\n: ${JSON.stringify(request, null, 2)}`); | ||
const requestToken: string = this.credentialRequestOpts.token; | ||
let response = (await post(credentialEndpoint, JSON.stringify(request), { bearerToken: requestToken })) as OpenIDResponse<CredentialResponse> & { | ||
|
||
let dPoP: string | undefined; | ||
if (createDPoPOptions) { | ||
const htu = credentialEndpoint.split('?')[0].split('#')[0]; | ||
dPoP = createDPoPOptions | ||
? await createDPoP({ | ||
...createDPoPOptions, | ||
jwtPayloadProps: { ...createDPoPOptions.jwtPayloadProps, htu, htm: 'POST', accessToken: requestToken }, | ||
}) | ||
: undefined; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see this repeated quite often. Maybe we can do the htu extracting in the createDpop method. That way we can do:
|
||
|
||
let response = (await post(credentialEndpoint, JSON.stringify(request), { | ||
bearerToken: requestToken, | ||
customHeaders: { ...(createDPoPOptions && { dpop: dPoP }) }, | ||
})) as OpenIDResponse<CredentialResponse> & { | ||
access_token: string; | ||
}; | ||
this._isDeferred = isDeferredCredentialResponse(response); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.