···99 try {
1010 // Parse request body to get PDS endpoint
1111 const body = await request.json();
1212- const pdsEndpoint = body.pdsEndpoint || DEFAULT_AUTH_SERVER;
1212+ let pdsEndpoint = body.pdsEndpoint || DEFAULT_AUTH_SERVER;
13131414- // Try to get a nonce from the specified PDS
1515- const tokenEndpoint = `${pdsEndpoint}/oauth/token`;
1414+ // CRITICAL FIX: Third-party PDS servers don't implement OAuth endpoints
1515+ // Always use bsky.social for OAuth operations
1616+ let authServer = pdsEndpoint;
1717+ if (!pdsEndpoint.includes('bsky.social')) {
1818+ console.log('[NONCE API] Redirecting to bsky.social for OAuth on third-party PDS');
1919+ authServer = DEFAULT_AUTH_SERVER;
2020+ }
2121+2222+ // Try to get a nonce from the auth server, not the PDS itself
2323+ const tokenEndpoint = `${authServer}/oauth/token`;
1624 console.log(`[NONCE API] Attempting to get nonce from: ${tokenEndpoint}`);
17251826 // Try multiple methods to get a nonce
+6-1
app/src/app/api/auth/token/route.ts
···3232 const { code, codeVerifier, dpopToken, pdsEndpoint } = body;
33333434 // Use the provided PDS endpoint or default to Bluesky's
3535- const authServer = pdsEndpoint || DEFAULT_AUTH_SERVER;
3535+ // CRITICAL FIX: For third-party PDS, always use bsky.social for token requests
3636+ let authServer = pdsEndpoint || DEFAULT_AUTH_SERVER;
3737+ if (pdsEndpoint && !pdsEndpoint.includes('bsky.social')) {
3838+ console.log(`Redirecting token request to bsky.social for third-party PDS: ${pdsEndpoint}`);
3939+ authServer = DEFAULT_AUTH_SERVER;
4040+ }
36413742 if (!code || !codeVerifier || !dpopToken) {
3843 return NextResponse.json(
+13-20
app/src/app/auth/callback/page.tsx
···117117 console.log('Exchanging code for token...');
118118 let tokenResponse;
119119 try {
120120- // For bsky.network endpoints, we used bsky.social as the auth server
121121- // but we'll use the actual PDS endpoint for API calls later
122122- if (storedAuthServer) {
123123- console.log('Using standard auth server for token exchange:', storedAuthServer);
124124- tokenResponse = await getAccessToken(
125125- code,
126126- codeVerifier,
127127- keyPair,
128128- storedAuthServer
129129- );
130130- } else {
131131- // For custom PDS endpoints, use the same endpoint for everything
132132- console.log('Using custom PDS for token exchange:', storedPdsEndpoint);
133133- tokenResponse = await getAccessToken(
134134- code,
135135- codeVerifier,
136136- keyPair,
137137- storedPdsEndpoint || undefined
138138- );
139139- }
120120+ // CRITICAL FIX: Use bsky.social for token exchange regardless of PDS host
121121+ // Third-party PDS servers don't implement OAuth endpoints
122122+ let authServer = storedAuthServer || 'https://bsky.social';
123123+124124+ // Always use bsky.social for token exchange (even for custom PDS endpoints)
125125+ console.log('Using auth server for token exchange:', authServer);
126126+127127+ tokenResponse = await getAccessToken(
128128+ code,
129129+ codeVerifier,
130130+ keyPair,
131131+ authServer
132132+ );
140133 } catch (tokenError: any) {
141134 console.error('Token exchange error:', tokenError);
142135 setError(`Failed to get access token: ${tokenError.message}`);
+23-5
app/src/lib/bluesky-api.ts
···73737474 console.log('[TOKEN REFRESH] Refreshing token for PDS:', pdsEndpoint);
75757676+ // CRITICAL FIX: For third-party PDS endpoints, use bsky.social for auth
7777+ // Third-party PDS hosts don't implement OAuth endpoints themselves
7878+ let authServer = pdsEndpoint;
7979+8080+ // Check if this is a third-party PDS (not bsky.social)
8181+ if (!pdsEndpoint.includes('bsky.social')) {
8282+ console.log('[TOKEN REFRESH] Using bsky.social for OAuth on third-party PDS');
8383+ authServer = 'https://bsky.social';
8484+ }
8585+7686 // Endpoint for token refresh
7777- const tokenEndpoint = `${pdsEndpoint}/oauth/token`;
8787+ const tokenEndpoint = `${authServer}/oauth/token`;
78887989 // First, ALWAYS get a fresh nonce before attempting token refresh
8090 let dpopNonce = null;
···271281 console.log('Access token is expired, attempting to refresh...');
272282273283 try {
274274- // Try to refresh the token
284284+ // Try to refresh the token using bsky.social for auth on third-party PDS
275285 const { accessToken: newAccessToken, refreshToken: newRefreshToken, dpopNonce: newNonce } =
276276- await refreshAccessToken(refreshToken, keyPair, pdsEndpoint);
286286+ await refreshAccessToken(refreshToken, keyPair, authServer);
277287278288 // Update tokens in localStorage
279289 localStorage.setItem('accessToken', newAccessToken);
···294304295305 console.log('Checking auth with PDS endpoint:', pdsEndpoint);
296306297297- // Use the PDS endpoint for auth check
307307+ // For API calls, use the actual PDS endpoint
298308 const baseUrl = `${pdsEndpoint}/xrpc`;
309309+310310+ // But when we need to do token refresh, use bsky.social for auth on third-party servers
311311+ let authServer = pdsEndpoint;
312312+ if (!pdsEndpoint.includes('bsky.social')) {
313313+ console.log('[AUTH CHECK] Will use bsky.social for OAuth on third-party PDS');
314314+ authServer = 'https://bsky.social';
315315+ }
299316300317 // First, get the user's handle from their DID using repo.describeRepo
301318 const describeRepoEndpoint = `${baseUrl}/com.atproto.repo.describeRepo`;
···376393377394 try {
378395 // Try to refresh the token with enhanced error handling
396396+ // Use authServer for token refresh (bsky.social for third-party PDS)
379397 const { accessToken: newAccessToken, refreshToken: newRefreshToken, dpopNonce: newNonce } =
380380- await refreshAccessToken(refreshToken, keyPair, pdsEndpoint);
398398+ await refreshAccessToken(refreshToken, keyPair, authServer);
381399382400 // Update tokens in localStorage
383401 if (typeof localStorage !== 'undefined') {