1+ import { createDPoP , CreateDPoPClientOpts , getCreateDPoPOptions } from '@sphereon/oid4vc-common' ;
12import {
23 AccessTokenRequest ,
34 AccessTokenRequestOpts ,
67 AuthorizationServerOpts ,
78 AuthzFlowType ,
89 convertJsonToURI ,
10+ DPoPResponseParams ,
911 EndpointMetadata ,
1012 formPost ,
1113 getIssuerFromCredentialOfferPayload ,
@@ -24,11 +26,12 @@ import { ObjectUtils } from '@sphereon/ssi-types';
2426
2527import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13' ;
2628import { createJwtBearerClientAssertion } from './functions' ;
29+ import { shouldRetryTokenRequestWithDPoPNonce } from './functions/dpopUtil' ;
2730import { LOG } from './types' ;
2831
2932export class AccessTokenClient {
30- public async acquireAccessToken ( opts : AccessTokenRequestOpts ) : Promise < OpenIDResponse < AccessTokenResponse > > {
31- const { asOpts, pin, codeVerifier, code, redirectUri, metadata } = opts ;
33+ public async acquireAccessToken ( opts : AccessTokenRequestOpts ) : Promise < OpenIDResponse < AccessTokenResponse , DPoPResponseParams > > {
34+ const { asOpts, pin, codeVerifier, code, redirectUri, metadata, createDPoPOpts } = opts ;
3235
3336 const credentialOffer = opts . credentialOffer ? await assertedUniformCredentialOffer ( opts . credentialOffer ) : undefined ;
3437 const pinMetadata : TxCodeAndPinRequired | undefined = credentialOffer && this . getPinMetadata ( credentialOffer . credential_offer ) ;
@@ -59,6 +62,7 @@ export class AccessTokenClient {
5962 metadata,
6063 asOpts,
6164 issuerOpts,
65+ createDPoPOpts : createDPoPOpts ,
6266 } ) ;
6367 }
6468
@@ -68,13 +72,15 @@ export class AccessTokenClient {
6872 metadata,
6973 asOpts,
7074 issuerOpts,
75+ createDPoPOpts,
7176 } : {
7277 accessTokenRequest : AccessTokenRequest ;
7378 pinMetadata ?: TxCodeAndPinRequired ;
7479 metadata ?: EndpointMetadata ;
7580 asOpts ?: AuthorizationServerOpts ;
7681 issuerOpts ?: IssuerOpts ;
77- } ) : Promise < OpenIDResponse < AccessTokenResponse > > {
82+ createDPoPOpts ?: CreateDPoPClientOpts ;
83+ } ) : Promise < OpenIDResponse < AccessTokenResponse , DPoPResponseParams > > {
7884 this . validate ( accessTokenRequest , pinMetadata ) ;
7985
8086 const requestTokenURL = AccessTokenClient . determineTokenURL ( {
@@ -87,10 +93,34 @@ export class AccessTokenClient {
8793 : undefined ,
8894 } ) ;
8995
90- return this . sendAuthCode ( requestTokenURL , accessTokenRequest ) ;
96+ const useDpop = createDPoPOpts ?. dPoPSigningAlgValuesSupported && createDPoPOpts . dPoPSigningAlgValuesSupported . length > 0 ;
97+ let dPoP = useDpop ? await createDPoP ( getCreateDPoPOptions ( createDPoPOpts , requestTokenURL ) ) : undefined ;
98+
99+ let response = await this . sendAuthCode ( requestTokenURL , accessTokenRequest , dPoP ? { headers : { dpop : dPoP } } : undefined ) ;
100+
101+ let nextDPoPNonce = createDPoPOpts ?. jwtPayloadProps . nonce ;
102+ const retryWithNonce = shouldRetryTokenRequestWithDPoPNonce ( response ) ;
103+ if ( retryWithNonce . ok && createDPoPOpts ) {
104+ createDPoPOpts . jwtPayloadProps . nonce = retryWithNonce . dpopNonce ;
105+
106+ dPoP = await createDPoP ( getCreateDPoPOptions ( createDPoPOpts , requestTokenURL ) ) ;
107+ response = await this . sendAuthCode ( requestTokenURL , accessTokenRequest , dPoP ? { headers : { dpop : dPoP } } : undefined ) ;
108+ const successDPoPNonce = response . origResponse . headers . get ( 'DPoP-Nonce' ) ;
109+
110+ nextDPoPNonce = successDPoPNonce ?? retryWithNonce . dpopNonce ;
111+ }
112+
113+ if ( response . successBody && createDPoPOpts && response . successBody . token_type !== 'DPoP' ) {
114+ throw new Error ( 'Invalid token type returned. Expected DPoP. Received: ' + response . successBody . token_type ) ;
115+ }
116+
117+ return {
118+ ...response ,
119+ params : { ...( nextDPoPNonce && { dpop : { dpopNonce : nextDPoPNonce } } ) } ,
120+ } ;
91121 }
92122
93- public async createAccessTokenRequest ( opts : AccessTokenRequestOpts ) : Promise < AccessTokenRequest > {
123+ public async createAccessTokenRequest ( opts : Omit < AccessTokenRequestOpts , 'createDPoPOpts' > ) : Promise < AccessTokenRequest > {
94124 const { asOpts, pin, codeVerifier, code, redirectUri } = opts ;
95125 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
96126 // @ts -ignore
@@ -105,6 +135,7 @@ export class AccessTokenClient {
105135 if ( credentialOfferRequest ?. supportedFlows . includes ( AuthzFlowType . PRE_AUTHORIZED_CODE_FLOW ) ) {
106136 this . assertAlphanumericPin ( opts . pinMetadata , pin ) ;
107137 request . user_pin = pin ;
138+ request . tx_code = pin ;
108139
109140 request . grant_type = GrantTypes . PRE_AUTHORIZED_CODE ;
110141 // we actually know it is there because of the isPreAuthCode call
@@ -221,8 +252,14 @@ export class AccessTokenClient {
221252 }
222253 }
223254
224- private async sendAuthCode ( requestTokenURL : string , accessTokenRequest : AccessTokenRequest ) : Promise < OpenIDResponse < AccessTokenResponse > > {
225- return await formPost ( requestTokenURL , convertJsonToURI ( accessTokenRequest , { mode : JsonURIMode . X_FORM_WWW_URLENCODED } ) ) ;
255+ private async sendAuthCode (
256+ requestTokenURL : string ,
257+ accessTokenRequest : AccessTokenRequest ,
258+ opts ?: { headers ?: Record < string , string > } ,
259+ ) : Promise < OpenIDResponse < AccessTokenResponse , DPoPResponseParams > > {
260+ return await formPost ( requestTokenURL , convertJsonToURI ( accessTokenRequest , { mode : JsonURIMode . X_FORM_WWW_URLENCODED } ) , {
261+ customHeaders : opts ?. headers ? opts . headers : undefined ,
262+ } ) ;
226263 }
227264
228265 public static determineTokenURL ( {
0 commit comments