···13131414 // Try to get a nonce from the specified PDS
1515 const tokenEndpoint = `${pdsEndpoint}/oauth/token`;
1616- console.log(`Attempting to get nonce from: ${tokenEndpoint}`);
1616+ console.log(`[NONCE API] Attempting to get nonce from: ${tokenEndpoint}`);
17171818- const response = await fetch(tokenEndpoint, {
1919- method: 'HEAD',
2020- headers: {
2121- 'Accept': '*/*'
2222- }
2323- });
1818+ // Try multiple methods to get a nonce
1919+ let nonce = null;
24202525- const nonce = response.headers.get('DPoP-Nonce');
2626-2727- if (nonce) {
2828- return NextResponse.json({ nonce });
2929- } else {
3030- // Try another method if HEAD doesn't work
3131- const probeResponse = await fetch(tokenEndpoint, {
3232- method: 'POST',
2121+ // Method 1: HEAD request (most efficient)
2222+ try {
2323+ console.log(`[NONCE API] Trying HEAD request to ${tokenEndpoint}`);
2424+ const headResponse = await fetch(tokenEndpoint, {
2525+ method: 'HEAD',
3326 headers: {
3434- 'Content-Type': 'application/x-www-form-urlencoded'
3535- },
3636- // Empty body to trigger an error response that might contain a nonce
3737- body: new URLSearchParams({})
2727+ 'Accept': '*/*'
2828+ }
3829 });
39304040- const probeNonce = probeResponse.headers.get('DPoP-Nonce');
4141-4242- if (probeNonce) {
4343- return NextResponse.json({ nonce: probeNonce });
4444- } else {
4545- return NextResponse.json(
4646- { error: 'Could not retrieve nonce', endpoint: tokenEndpoint },
4747- { status: 404 }
4848- );
3131+ nonce = headResponse.headers.get('DPoP-Nonce');
3232+ if (nonce) {
3333+ console.log(`[NONCE API] Got nonce via HEAD request: ${nonce}`);
4934 }
3535+ } catch (headError) {
3636+ console.warn(`[NONCE API] HEAD request failed:`, headError);
5037 }
3838+3939+ // Method 2: OPTIONS request if HEAD fails
4040+ if (!nonce) {
4141+ try {
4242+ console.log(`[NONCE API] Trying OPTIONS request to ${tokenEndpoint}`);
4343+ const optionsResponse = await fetch(tokenEndpoint, {
4444+ method: 'OPTIONS',
4545+ headers: {
4646+ 'Accept': '*/*'
4747+ }
4848+ });
4949+5050+ nonce = optionsResponse.headers.get('DPoP-Nonce');
5151+ if (nonce) {
5252+ console.log(`[NONCE API] Got nonce via OPTIONS request: ${nonce}`);
5353+ }
5454+ } catch (optionsError) {
5555+ console.warn(`[NONCE API] OPTIONS request failed:`, optionsError);
5656+ }
5757+ }
5858+5959+ // Method 3: POST probe (last resort)
6060+ if (!nonce) {
6161+ try {
6262+ console.log(`[NONCE API] Trying POST probe to ${tokenEndpoint}`);
6363+ const probeResponse = await fetch(tokenEndpoint, {
6464+ method: 'POST',
6565+ headers: {
6666+ 'Content-Type': 'application/x-www-form-urlencoded'
6767+ },
6868+ // Empty body to trigger an error response that might contain a nonce
6969+ body: new URLSearchParams({})
7070+ });
7171+7272+ nonce = probeResponse.headers.get('DPoP-Nonce');
7373+ if (nonce) {
7474+ console.log(`[NONCE API] Got nonce via POST probe: ${nonce}`);
7575+ }
7676+ } catch (probeError) {
7777+ console.warn(`[NONCE API] POST probe failed:`, probeError);
7878+ }
7979+ }
8080+8181+ // If we got a nonce through any method, return it
8282+ if (nonce) {
8383+ return NextResponse.json({ nonce });
8484+ }
8585+8686+ // If all methods failed, return an error
8787+ console.log(`[NONCE API] All methods failed to get a nonce from ${tokenEndpoint}`);
8888+ return NextResponse.json(
8989+ { error: 'Could not retrieve nonce', endpoint: tokenEndpoint },
9090+ { status: 404 }
9191+ );
5192 } catch (error: any) {
5252- console.error('Nonce retrieval error:', error);
9393+ console.error('[NONCE API] Nonce retrieval error:', error);
5394 return NextResponse.json(
5495 { error: 'Nonce retrieval error', message: error.message },
5596 { status: 500 }
···62103 try {
63104 // Use the default Bluesky server
64105 const tokenEndpoint = `${DEFAULT_AUTH_SERVER}/oauth/token`;
6565- const response = await fetch(tokenEndpoint, {
6666- method: 'HEAD',
6767- headers: {
6868- 'Accept': '*/*'
6969- }
7070- });
106106+ console.log(`[NONCE API] GET: Attempting to get nonce from: ${tokenEndpoint}`);
711077272- const nonce = response.headers.get('DPoP-Nonce');
108108+ // Try multiple methods to get a nonce
109109+ let nonce = null;
731107474- if (nonce) {
7575- return NextResponse.json({ nonce });
7676- } else {
7777- // Try another method if HEAD doesn't work
7878- const probeResponse = await fetch(tokenEndpoint, {
7979- method: 'POST',
111111+ // Method 1: HEAD request (most efficient)
112112+ try {
113113+ console.log(`[NONCE API] GET: Trying HEAD request to ${tokenEndpoint}`);
114114+ const headResponse = await fetch(tokenEndpoint, {
115115+ method: 'HEAD',
80116 headers: {
8181- 'Content-Type': 'application/x-www-form-urlencoded'
8282- },
8383- // Empty body to trigger an error response that might contain a nonce
8484- body: new URLSearchParams({})
117117+ 'Accept': '*/*'
118118+ }
85119 });
861208787- const probeNonce = probeResponse.headers.get('DPoP-Nonce');
8888-8989- if (probeNonce) {
9090- return NextResponse.json({ nonce: probeNonce });
9191- } else {
9292- return NextResponse.json(
9393- { error: 'Could not retrieve nonce' },
9494- { status: 404 }
9595- );
121121+ nonce = headResponse.headers.get('DPoP-Nonce');
122122+ if (nonce) {
123123+ console.log(`[NONCE API] GET: Got nonce via HEAD request: ${nonce}`);
96124 }
125125+ } catch (headError) {
126126+ console.warn(`[NONCE API] GET: HEAD request failed:`, headError);
97127 }
128128+129129+ // Method 2: OPTIONS request if HEAD fails
130130+ if (!nonce) {
131131+ try {
132132+ console.log(`[NONCE API] GET: Trying OPTIONS request to ${tokenEndpoint}`);
133133+ const optionsResponse = await fetch(tokenEndpoint, {
134134+ method: 'OPTIONS',
135135+ headers: {
136136+ 'Accept': '*/*'
137137+ }
138138+ });
139139+140140+ nonce = optionsResponse.headers.get('DPoP-Nonce');
141141+ if (nonce) {
142142+ console.log(`[NONCE API] GET: Got nonce via OPTIONS request: ${nonce}`);
143143+ }
144144+ } catch (optionsError) {
145145+ console.warn(`[NONCE API] GET: OPTIONS request failed:`, optionsError);
146146+ }
147147+ }
148148+149149+ // Method 3: POST probe (last resort)
150150+ if (!nonce) {
151151+ try {
152152+ console.log(`[NONCE API] GET: Trying POST probe to ${tokenEndpoint}`);
153153+ const probeResponse = await fetch(tokenEndpoint, {
154154+ method: 'POST',
155155+ headers: {
156156+ 'Content-Type': 'application/x-www-form-urlencoded'
157157+ },
158158+ // Empty body to trigger an error response that might contain a nonce
159159+ body: new URLSearchParams({})
160160+ });
161161+162162+ nonce = probeResponse.headers.get('DPoP-Nonce');
163163+ if (nonce) {
164164+ console.log(`[NONCE API] GET: Got nonce via POST probe: ${nonce}`);
165165+ }
166166+ } catch (probeError) {
167167+ console.warn(`[NONCE API] GET: POST probe failed:`, probeError);
168168+ }
169169+ }
170170+171171+ // If we got a nonce through any method, return it
172172+ if (nonce) {
173173+ return NextResponse.json({ nonce });
174174+ }
175175+176176+ // If all methods failed, return an error
177177+ console.log(`[NONCE API] GET: All methods failed to get a nonce from ${tokenEndpoint}`);
178178+ return NextResponse.json(
179179+ { error: 'Could not retrieve nonce', endpoint: tokenEndpoint },
180180+ { status: 404 }
181181+ );
98182 } catch (error: any) {
9999- console.error('Nonce retrieval error:', error);
183183+ console.error('[NONCE API] GET: Nonce retrieval error:', error);
100184 return NextResponse.json(
101185 { error: 'Nonce retrieval error', message: error.message },
102186 { status: 500 }
+14-1
app/src/app/auth/callback/page.tsx
···148148 return;
149149 }
150150151151- // Save the DPoP nonce from the response headers if present
151151+ // Save the DPoP nonce from the response
152152 let dpopNonce = null;
153153+154154+ // First check if it's in the response object
153155 if (tokenResponse.dpop_nonce) {
154156 dpopNonce = tokenResponse.dpop_nonce;
157157+ console.log('Retrieved DPoP nonce from token response:', dpopNonce);
158158+ }
159159+160160+ // If not found in the response object, check localStorage
161161+ // This is useful for third-party PDS servers
162162+ if (!dpopNonce && typeof localStorage !== 'undefined') {
163163+ const storedNonce = localStorage.getItem('dpopNonce');
164164+ if (storedNonce) {
165165+ console.log('Retrieved DPoP nonce from localStorage:', storedNonce);
166166+ dpopNonce = storedNonce;
167167+ }
155168 }
156169157170 setStatus('Getting user profile...');
+53-15
app/src/lib/auth-context.tsx
···53535454 // Check if token is expired or expiring soon
5555 if (isTokenExpired(accessToken)) {
5656- console.log('Access token is expired or expiring soon, refreshing...');
5656+ console.log('[AUTH CONTEXT] Access token is expired or expiring soon, refreshing...');
57575858 // Deserialize keypair
5959 const keyPairData = JSON.parse(serializedKeyPair);
···7373 );
7474 const keyPair = { publicKey, privateKey };
75757676- // Refresh the token
7777- const { accessToken: newAccessToken, refreshToken: newRefreshToken, dpopNonce: newNonce } =
7878- await refreshAccessToken(refreshToken, keyPair, pdsEndpoint);
7979-8080- // Update state and localStorage
8181- setAccessToken(newAccessToken);
8282- setRefreshToken(newRefreshToken);
8383- if (newNonce) setDpopNonce(newNonce);
7676+ // Get the current DPoP nonce from localStorage if available
7777+ let currentNonce = dpopNonce;
7878+ if (!currentNonce && typeof localStorage !== 'undefined') {
7979+ currentNonce = localStorage.getItem('dpopNonce');
8080+ if (currentNonce) {
8181+ console.log('[AUTH CONTEXT] Retrieved nonce from localStorage:', currentNonce);
8282+ // Update state with the nonce from localStorage
8383+ setDpopNonce(currentNonce);
8484+ }
8585+ }
84868585- localStorage.setItem('accessToken', newAccessToken);
8686- localStorage.setItem('refreshToken', newRefreshToken);
8787- if (newNonce) localStorage.setItem('dpopNonce', newNonce);
8787+ console.log('[AUTH CONTEXT] Refreshing token for PDS:', pdsEndpoint);
88888989- setLastTokenRefresh(Date.now());
9090- console.log('Successfully refreshed access token');
8989+ // Refresh the token with enhanced error handling
9090+ try {
9191+ const { accessToken: newAccessToken, refreshToken: newRefreshToken, dpopNonce: newNonce } =
9292+ await refreshAccessToken(refreshToken, keyPair, pdsEndpoint);
9393+9494+ // Update state
9595+ setAccessToken(newAccessToken);
9696+ setRefreshToken(newRefreshToken);
9797+ if (newNonce) setDpopNonce(newNonce);
9898+9999+ // Update localStorage
100100+ if (typeof localStorage !== 'undefined') {
101101+ localStorage.setItem('accessToken', newAccessToken);
102102+ localStorage.setItem('refreshToken', newRefreshToken);
103103+ if (newNonce) localStorage.setItem('dpopNonce', newNonce);
104104+ }
105105+106106+ setLastTokenRefresh(Date.now());
107107+ console.log('[AUTH CONTEXT] Successfully refreshed access token');
108108+ } catch (refreshError) {
109109+ console.error('[AUTH CONTEXT] Token refresh failed:', refreshError);
110110+111111+ // If refresh fails, we'll still try to use any nonce we received
112112+ if (typeof localStorage !== 'undefined') {
113113+ const latestNonce = localStorage.getItem('dpopNonce');
114114+ if (latestNonce && latestNonce !== dpopNonce) {
115115+ console.log('[AUTH CONTEXT] Using latest nonce from localStorage:', latestNonce);
116116+ setDpopNonce(latestNonce);
117117+ }
118118+ }
119119+ }
120120+ } else {
121121+ // Even if token is not expired, make sure we have the latest nonce
122122+ if (typeof localStorage !== 'undefined') {
123123+ const latestNonce = localStorage.getItem('dpopNonce');
124124+ if (latestNonce && latestNonce !== dpopNonce) {
125125+ console.log('[AUTH CONTEXT] Updating nonce from localStorage:', latestNonce);
126126+ setDpopNonce(latestNonce);
127127+ }
128128+ }
91129 }
92130 } catch (error) {
9393- console.error('Failed to refresh token:', error);
131131+ console.error('[AUTH CONTEXT] Error in checkAndRefreshToken:', error);
94132 }
95133 };
96134
+145-37
app/src/lib/bluesky-api.ts
···7171 throw new Error('No PDS endpoint provided for token refresh');
7272 }
73737474- console.log('Refreshing access token with PDS endpoint:', pdsEndpoint);
7474+ console.log('[TOKEN REFRESH] Refreshing token for PDS:', pdsEndpoint);
75757676 // Endpoint for token refresh
7777 const tokenEndpoint = `${pdsEndpoint}/oauth/token`;
78787979- // Get a nonce for the DPoP token
7979+ // First, ALWAYS get a fresh nonce before attempting token refresh
8080 let dpopNonce = null;
8181 try {
8282- // Try to get a nonce with a HEAD request
8383- const headResponse = await fetch(tokenEndpoint, {
8484- method: 'HEAD'
8282+ // Try server-side nonce retrieval first
8383+ console.log('[TOKEN REFRESH] Getting fresh nonce from server API');
8484+ const nonceResponse = await fetch('/api/auth/nonce', {
8585+ method: 'POST',
8686+ headers: { 'Content-Type': 'application/json' },
8787+ body: JSON.stringify({ pdsEndpoint })
8588 });
86898787- dpopNonce = headResponse.headers.get('DPoP-Nonce');
8888- if (dpopNonce) {
8989- console.log('Got nonce for token refresh:', dpopNonce);
9090+ if (nonceResponse.ok) {
9191+ const nonceData = await nonceResponse.json();
9292+ if (nonceData.nonce) {
9393+ dpopNonce = nonceData.nonce;
9494+ console.log('[TOKEN REFRESH] Got fresh nonce from server API:', dpopNonce);
9595+ }
9696+ }
9797+9898+ // If server-side retrieval fails, try client-side
9999+ if (!dpopNonce) {
100100+ console.log('[TOKEN REFRESH] Trying HEAD request for nonce');
101101+ const headResponse = await fetch(tokenEndpoint, { method: 'HEAD' });
102102+ dpopNonce = headResponse.headers.get('DPoP-Nonce');
103103+ }
104104+105105+ // If still no nonce, try POST probe
106106+ if (!dpopNonce) {
107107+ console.log('[TOKEN REFRESH] Trying POST probe for nonce');
108108+ const probeResponse = await fetch(tokenEndpoint, {
109109+ method: 'POST',
110110+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
111111+ body: new URLSearchParams({}) // Empty body to trigger error response with nonce
112112+ });
113113+ dpopNonce = probeResponse.headers.get('DPoP-Nonce');
90114 }
91115 } catch (nonceError) {
9292- console.warn('Failed to get nonce for token refresh:', nonceError);
116116+ console.warn('[TOKEN REFRESH] Failed to get initial nonce:', nonceError);
117117+ }
118118+119119+ if (!dpopNonce) {
120120+ console.log('[TOKEN REFRESH] No nonce obtained, proceeding without one');
121121+ } else {
122122+ console.log('[TOKEN REFRESH] Obtained nonce:', dpopNonce);
93123 }
9412495125 // Generate DPoP token for the refresh request
···99129 publicKey,
100130 'POST',
101131 tokenEndpoint,
102102- dpopNonce || undefined // Convert null to undefined to satisfy TypeScript
132132+ dpopNonce || undefined
103133 );
134134+135135+ console.log('[TOKEN REFRESH] Making token refresh request');
104136105137 // Make the token refresh request
106138 const response = await fetch(tokenEndpoint, {
···116148 })
117149 });
118150119119- // Check for nonce error
151151+ // Handle nonce error explicitly - this is the critical part!
120152 if (response.status === 401) {
153153+ let responseBody;
154154+ try {
155155+ responseBody = await response.json();
156156+ } catch (e) {
157157+ responseBody = {};
158158+ }
159159+121160 const newNonce = response.headers.get('DPoP-Nonce');
122122- if (newNonce) {
123123- console.log('Got new nonce during token refresh:', newNonce);
161161+162162+ // Check for DPoP nonce error
163163+ if (
164164+ (responseBody.error === 'use_dpop_nonce' ||
165165+ (responseBody.error_description && responseBody.error_description.includes('nonce'))) &&
166166+ newNonce
167167+ ) {
168168+ console.log('[TOKEN REFRESH] Received nonce error, retrying with new nonce:', newNonce);
124169125125- // Try again with the new nonce
170170+ // Generate new DPoP token with the provided nonce
126171 const retryDpopToken = await generateDPoPToken(
127172 keyPair.privateKey,
128173 publicKey,
···131176 newNonce
132177 );
133178179179+ console.log('[TOKEN REFRESH] Retrying token refresh with new nonce');
180180+181181+ // Retry the request with the new nonce
134182 const retryResponse = await fetch(tokenEndpoint, {
135183 method: 'POST',
136184 headers: {
···146194147195 if (!retryResponse.ok) {
148196 const errorText = await retryResponse.text();
197197+ console.error('[TOKEN REFRESH] Token refresh retry failed:', retryResponse.status, errorText);
149198 throw new Error(`Token refresh retry failed: ${retryResponse.status}, ${errorText}`);
150199 }
151200152201 const tokenData = await retryResponse.json();
202202+ console.log('[TOKEN REFRESH] Successfully refreshed token on retry');
203203+204204+ // Store the new nonce for future requests
205205+ if (typeof localStorage !== 'undefined') {
206206+ localStorage.setItem('dpopNonce', newNonce);
207207+ }
153208154209 // Return the new tokens and nonce
155210 return {
156211 accessToken: tokenData.access_token,
157157- refreshToken: tokenData.refresh_token || refreshToken, // Use the new refresh token if provided
212212+ refreshToken: tokenData.refresh_token || refreshToken,
158213 dpopNonce: newNonce
159214 };
160215 }
···162217163218 if (!response.ok) {
164219 const errorText = await response.text();
220220+ console.error('[TOKEN REFRESH] Token refresh failed:', response.status, errorText);
165221 throw new Error(`Token refresh failed: ${response.status}, ${errorText}`);
166222 }
167223···170226 // Get any nonce from the response headers
171227 const responseNonce = response.headers.get('DPoP-Nonce');
172228229229+ console.log('[TOKEN REFRESH] Successfully refreshed access token');
230230+231231+ // Update the nonce in localStorage
232232+ if (responseNonce && typeof localStorage !== 'undefined') {
233233+ localStorage.setItem('dpopNonce', responseNonce);
234234+ }
235235+173236 // Return the new tokens and nonce
174237 return {
175238 accessToken: tokenData.access_token,
176176- refreshToken: tokenData.refresh_token || refreshToken, // Use the new refresh token if provided
177177- dpopNonce: responseNonce || dpopNonce || undefined
239239+ refreshToken: tokenData.refresh_token || refreshToken,
240240+ dpopNonce: responseNonce || dpopNonce
178241 };
179242 } catch (error) {
180180- console.error('Error refreshing access token:', error);
243243+ console.error('[TOKEN REFRESH] Error refreshing access token:', error);
181244 throw error;
182245 }
183246}
···280343 }
281344282345 if (response.status === 401) {
346346+ // Try to parse error response
347347+ let responseBody;
348348+ try {
349349+ responseBody = await response.json();
350350+ } catch (e) {
351351+ responseBody = {};
352352+ }
353353+283354 const nonce = response.headers.get('DPoP-Nonce');
284355 if (nonce) {
285285- console.log('Got nonce during auth check:', nonce);
356356+ console.log('[AUTH CHECK] Got nonce during auth check:', nonce);
357357+358358+ // Store the nonce for future use
359359+ if (typeof localStorage !== 'undefined') {
360360+ localStorage.setItem('dpopNonce', nonce);
361361+ }
362362+363363+ // Check if this is a nonce error
364364+ if (responseBody.error === 'use_dpop_nonce' ||
365365+ (responseBody.error_description && responseBody.error_description.includes('nonce'))) {
366366+ console.log('[AUTH CHECK] DPoP nonce error detected, retrying with new nonce');
367367+ }
368368+286369 // Try again with the nonce, but prevent infinite recursion
287370 return checkAuth(accessToken, keyPair, did, nonce, pdsEndpoint, refreshToken);
288371 }
289372290373 // If we have a refresh token, try to refresh the access token
291374 if (refreshToken && !tokenExpired) { // Only try this if we didn't already try above
292292- console.log('Auth failed with 401, attempting to refresh token...');
375375+ console.log('[AUTH CHECK] Auth failed with 401, attempting to refresh token...');
293376294377 try {
295295- // Try to refresh the token
378378+ // Try to refresh the token with enhanced error handling
296379 const { accessToken: newAccessToken, refreshToken: newRefreshToken, dpopNonce: newNonce } =
297380 await refreshAccessToken(refreshToken, keyPair, pdsEndpoint);
298381299382 // Update tokens in localStorage
300300- localStorage.setItem('accessToken', newAccessToken);
301301- localStorage.setItem('refreshToken', newRefreshToken);
302302- if (newNonce) localStorage.setItem('dpopNonce', newNonce);
383383+ if (typeof localStorage !== 'undefined') {
384384+ localStorage.setItem('accessToken', newAccessToken);
385385+ localStorage.setItem('refreshToken', newRefreshToken);
386386+ if (newNonce) localStorage.setItem('dpopNonce', newNonce);
387387+388388+ console.log('[AUTH CHECK] Tokens updated in localStorage during checkAuth');
389389+ }
303390304304- console.log('Tokens updated in localStorage during checkAuth');
305305-306306- console.log('Token refreshed successfully, retrying auth check with new token');
391391+ console.log('[AUTH CHECK] Token refreshed successfully, retrying auth check with new token');
307392308393 // Return the result of checkAuth with the new token
309394 return checkAuth(newAccessToken, keyPair, did, newNonce || null, pdsEndpoint, newRefreshToken);
310395 } catch (refreshError) {
311311- console.error('Token refresh failed:', refreshError);
396396+ console.error('[AUTH CHECK] Token refresh failed:', refreshError);
397397+ console.log('[AUTH CHECK] User needs to re-authenticate - session cannot be restored');
312398 }
313399 }
314400 }
···393479 responseBody = {};
394480 }
395481482482+ // Get the nonce from headers if available
483483+ const newDpopNonce = response.headers.get('DPoP-Nonce');
484484+396485 // Check if this is a nonce error
397486 if (
398398- responseBody.error === 'use_dpop_nonce' ||
399399- (responseBody.error_description && responseBody.error_description.includes('nonce'))
487487+ (responseBody.error === 'use_dpop_nonce' ||
488488+ (responseBody.error_description && responseBody.error_description.includes('nonce'))) &&
489489+ newDpopNonce
400490 ) {
401401- // Get new nonce from header
402402- const newDpopNonce = response.headers.get('DPoP-Nonce');
403403- if (newDpopNonce) {
404404- console.log('Retrying API request with nonce:', newDpopNonce);
405405- return makeAuthenticatedRequest(endpoint, method, accessToken, keyPair, newDpopNonce, body);
491491+ // Store the nonce for future use
492492+ if (typeof localStorage !== 'undefined') {
493493+ console.log('[API REQUEST] Storing new DPoP nonce in localStorage:', newDpopNonce);
494494+ localStorage.setItem('dpopNonce', newDpopNonce);
406495 }
496496+497497+ console.log('[API REQUEST] Retrying request with new nonce:', newDpopNonce);
498498+ return makeAuthenticatedRequest(endpoint, method, accessToken, keyPair, newDpopNonce, body, pdsEndpoint);
407499 }
408500409501 // Other 401 error, possibly expired token
502502+ console.error('[API REQUEST] Request failed with 401 unauthorized:', responseBody);
503503+504504+ // Include nonce in error message if available
505505+ if (newDpopNonce) {
506506+ console.log('[API REQUEST] 401 response included nonce:', newDpopNonce);
507507+ // Store the nonce even though we're not retrying now
508508+ if (typeof localStorage !== 'undefined') {
509509+ localStorage.setItem('dpopNonce', newDpopNonce);
510510+ }
511511+ }
512512+410513 throw new Error(`API request unauthorized: ${JSON.stringify(responseBody)}`);
411514 }
412515···433536 // Save any nonce for future requests
434537 const returnNonce = response.headers.get('DPoP-Nonce');
435538 if (returnNonce && returnNonce !== dpopNonce) {
436436- // If there's a place to store it, we would store it here
437437- console.log('New DPoP nonce received:', returnNonce);
539539+ console.log('[API REQUEST] New DPoP nonce received in successful response:', returnNonce);
540540+541541+ // Always store the latest nonce for future requests
542542+ if (typeof localStorage !== 'undefined') {
543543+ localStorage.setItem('dpopNonce', returnNonce);
544544+ console.log('[API REQUEST] Updated nonce in localStorage for future requests');
545545+ }
438546 }
439547440548 return result;