Skip to content

Commit 2246c51

Browse files
committed
fix: reset auth coordinator state to prevent stale auth code reuse
The issue was that when invalid_client errors occurred during token exchange, the auth restart was using cached auth coordinator state with stale auth codes. Changes: - Add resetAuth() method to AuthCoordinator interface - Implement auth state reset in createLazyAuthCoordinator - Call resetAuth() before restarting auth flow on invalid_client errors - Close existing auth server during reset to prevent port conflicts This ensures fresh browser auth with new auth codes instead of reusing stale ones that have already failed token exchange.
1 parent 290fe57 commit 2246c51

File tree

5 files changed

+31
-3
lines changed

5 files changed

+31
-3
lines changed

DEFENSIVE_AUTH_CHANGES.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ When the scenario occurs again:
6262
2. Classifies this as `invalid_client` error
6363
3. Logs clear message about client registration being deleted
6464
4. Clears ALL stored auth data (tokens, client_info.json, code_verifier.txt)
65-
5. **Immediately starts fresh auth flow** (no manual restart required)
66-
6. Opens browser for new client registration and authentication
67-
7. Completes with working connection automatically
65+
5. **Resets auth coordinator state** to force fresh browser auth (no stale auth codes)
66+
6. **Immediately starts fresh auth flow** (no manual restart required)
67+
7. Opens NEW browser window for new client registration and authentication
68+
8. Completes with working connection automatically
6869

6970
## Testing
7071

src/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ async function runClient(
8888
return {
8989
waitForAuthCode: authState.waitForAuthCode,
9090
skipBrowserAuth: authState.skipBrowserAuth,
91+
resetAuth: () => authCoordinator.resetAuth(),
9192
}
9293
}
9394

src/lib/coordination.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { log, debugLog, DEBUG, setupOAuthCallbackServerWithLongPoll } from './ut
88

99
export type AuthCoordinator = {
1010
initializeAuth: () => Promise<{ server: Server; waitForAuthCode: () => Promise<string>; skipBrowserAuth: boolean }>
11+
resetAuth: () => void
1112
}
1213

1314
/**
@@ -152,6 +153,19 @@ export function createLazyAuthCoordinator(serverUrlHash: string, callbackPort: n
152153
if (DEBUG) debugLog('Auth coordination completed', { skipBrowserAuth: authState.skipBrowserAuth })
153154
return authState
154155
},
156+
resetAuth: () => {
157+
if (DEBUG) debugLog('Resetting auth coordinator state')
158+
if (authState) {
159+
// Close the existing server if it exists
160+
try {
161+
authState.server.close()
162+
if (DEBUG) debugLog('Closed existing auth server during reset')
163+
} catch (error) {
164+
if (DEBUG) debugLog('Error closing auth server during reset', error)
165+
}
166+
}
167+
authState = null
168+
},
155169
}
156170
}
157171

src/lib/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo
273273
export type AuthInitializer = () => Promise<{
274274
waitForAuthCode: () => Promise<string>
275275
skipBrowserAuth: boolean
276+
resetAuth: () => void
276277
}>
277278

278279
/**
@@ -429,6 +430,11 @@ export async function connectToRemoteServer(
429430
await extendedProvider.clearAllAuthData()
430431
}
431432

433+
// Reset auth coordinator state to force fresh browser auth
434+
if (DEBUG) debugLog('Resetting auth coordinator to force fresh browser auth')
435+
const { resetAuth } = await authInitializer()
436+
resetAuth()
437+
432438
// Reset retry state since we're starting fresh
433439
resetAuthRetryState(serverUrlHash)
434440
}
@@ -509,6 +515,11 @@ export async function connectToRemoteServer(
509515
await extendedProvider.clearAllAuthData()
510516
}
511517

518+
// Reset auth coordinator state to force fresh browser auth
519+
if (DEBUG) debugLog('Resetting auth coordinator to force fresh browser auth')
520+
const { resetAuth: resetAuthFirst } = await authInitializer()
521+
resetAuthFirst()
522+
512523
// Reset retry state since we're starting fresh
513524
resetAuthRetryState(serverUrlHash)
514525

src/proxy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ async function runProxy(
8080
return {
8181
waitForAuthCode: authState.waitForAuthCode,
8282
skipBrowserAuth: authState.skipBrowserAuth,
83+
resetAuth: () => authCoordinator.resetAuth(),
8384
}
8485
}
8586

0 commit comments

Comments
 (0)