@@ -246,6 +246,7 @@ export class AuthClient {
246246
247247 private dpopKeyPair ?: DpopKeyPair ;
248248 private readonly useDPoP : boolean ;
249+ private defaultDPoPHandle ?: ReturnType < typeof oauth . DPoP > ;
249250
250251 constructor ( options : AuthClientOptions ) {
251252 // dependencies
@@ -366,6 +367,12 @@ export class AuthClient {
366367 // Initialize DPoP if enabled. Check useDPoP flag first to avoid timing attacks.
367368 if ( ( options . useDPoP ?? false ) && options . dpopKeyPair ) {
368369 this . dpopKeyPair = options . dpopKeyPair ;
370+ // Create DPoP handle once for reuse across all token grant operations
371+ // oauth4webapi automatically learns nonces from error responses
372+ this . defaultDPoPHandle = oauth . DPoP (
373+ this . clientMetadata ,
374+ options . dpopKeyPair
375+ ) ;
369376 }
370377 }
371378
@@ -772,14 +779,17 @@ export class AuthClient {
772779 ...this . httpOptions ( ) ,
773780 [ oauth . customFetch ] : this . fetch ,
774781 [ oauth . allowInsecureRequests ] : this . allowInsecureRequests ,
775- ...( this . useDPoP &&
776- this . dpopKeyPair && {
777- DPoP : oauth . DPoP ( this . clientMetadata , this . dpopKeyPair ! )
778- } )
782+ ...( this . defaultDPoPHandle && { DPoP : this . defaultDPoPHandle } )
779783 }
780784 ) ;
781785
782- codeGrantResponse = await authorizationCodeGrantRequestCall ( ) ;
786+ codeGrantResponse = await withDPoPNonceRetry (
787+ authorizationCodeGrantRequestCall ,
788+ {
789+ isDPoPEnabled : ! ! ( this . useDPoP && this . dpopKeyPair ) ,
790+ ...this . dpopOptions ?. retry
791+ }
792+ ) ;
783793 } catch ( e : any ) {
784794 return this . handleCallbackError (
785795 new AuthorizationCodeGrantRequestError ( e . message ) ,
@@ -791,8 +801,9 @@ export class AuthClient {
791801 let oidcRes : oauth . TokenEndpointResponse ;
792802 try {
793803 // Process the authorization code response
794- // For authorization code flows, oauth4webapi handles DPoP nonce management internally
795- // No need for manual retry since authorization codes are single-use
804+ // When DPoP is enabled, the nonce retry logic is handled above in the
805+ // authorizationCodeGrantRequestCall, so codeGrantResponse is guaranteed
806+ // to have been obtained with proper nonce handling if needed
796807 oidcRes = await oauth . processAuthorizationCodeResponse (
797808 authorizationServerMetadata ,
798809 this . clientMetadata ,
@@ -1213,10 +1224,7 @@ export class AuthClient {
12131224 [ oauth . customFetch ] : this . fetch ,
12141225 [ oauth . allowInsecureRequests ] : this . allowInsecureRequests ,
12151226 additionalParameters,
1216- ...( this . useDPoP &&
1217- this . dpopKeyPair && {
1218- DPoP : oauth . DPoP ( this . clientMetadata , this . dpopKeyPair ! )
1219- } )
1227+ ...( this . defaultDPoPHandle && { DPoP : this . defaultDPoPHandle } )
12201228 }
12211229 ) ;
12221230
@@ -1229,10 +1237,16 @@ export class AuthClient {
12291237
12301238 let oauthRes : oauth . TokenEndpointResponse ;
12311239 try {
1232- oauthRes = await withDPoPNonceRetry ( async ( ) => {
1233- const refreshTokenRes = await refreshTokenGrantRequestCall ( ) ;
1234- return await processRefreshTokenResponseCall ( refreshTokenRes ) ;
1235- } , this . dpopOptions ?. retry ) ;
1240+ oauthRes = await withDPoPNonceRetry (
1241+ async ( ) => {
1242+ const refreshTokenRes = await refreshTokenGrantRequestCall ( ) ;
1243+ return await processRefreshTokenResponseCall ( refreshTokenRes ) ;
1244+ } ,
1245+ {
1246+ isDPoPEnabled : ! ! ( this . useDPoP && this . dpopKeyPair ) ,
1247+ ...this . dpopOptions ?. retry
1248+ }
1249+ ) ;
12361250 } catch ( e : any ) {
12371251 return [
12381252 new AccessTokenError (
@@ -1755,26 +1769,28 @@ export class AuthClient {
17551769 {
17561770 [ oauth . customFetch ] : this . fetch ,
17571771 [ oauth . allowInsecureRequests ] : this . allowInsecureRequests ,
1758- ...( this . useDPoP &&
1759- this . dpopKeyPair && {
1760- DPoP : oauth . DPoP ( this . clientMetadata , this . dpopKeyPair ! )
1761- } )
1772+ ...( this . defaultDPoPHandle && { DPoP : this . defaultDPoPHandle } )
17621773 }
17631774 ) ;
17641775
1765- const processGenericTokenEndpointResponseCall = ( response : Response ) =>
1766- oauth . processGenericTokenEndpointResponse (
1776+ const processGenericTokenEndpointResponseCall = async ( ) => {
1777+ const httpResponse = await genericTokenEndpointRequestCall ( ) ;
1778+ return oauth . processGenericTokenEndpointResponse (
17671779 authorizationServerMetadata ,
17681780 this . clientMetadata ,
1769- response
1781+ httpResponse
17701782 ) ;
1783+ } ;
17711784
17721785 let tokenEndpointResponse : oauth . TokenEndpointResponse ;
17731786 try {
1774- tokenEndpointResponse = await withDPoPNonceRetry ( async ( ) => {
1775- const httpResponse = await genericTokenEndpointRequestCall ( ) ;
1776- return await processGenericTokenEndpointResponseCall ( httpResponse ) ;
1777- } , this . dpopOptions ?. retry ) ;
1787+ tokenEndpointResponse = await withDPoPNonceRetry (
1788+ processGenericTokenEndpointResponseCall ,
1789+ {
1790+ isDPoPEnabled : ! ! ( this . useDPoP && this . dpopKeyPair ) ,
1791+ ...this . dpopOptions ?. retry
1792+ }
1793+ ) ;
17781794 } catch ( err : any ) {
17791795 return [
17801796 new AccessTokenForConnectionError (
0 commit comments