This document describes OAuth token management bugs discovered during OAuth E2E testing and their fixes.
After a successful OAuth login, if the access token expires and mcpproxy attempts to reconnect, the server remains in "disconnected" status with error:
failed to connect: all authentication strategies failed, last error: OAuth authorization required - deferred for background processing
When mcpproxy reconnects to an OAuth-protected server:
- It detects there's a persisted token (with
token_expires_atin the past) - It creates a NEW OAuth config with an empty token store
- The existing persisted token is NOT loaded into the OAuth transport
- When MCP initialize is called, the transport has no token available
- The request fails with "no valid token available, authorization required"
-
Added
HasPersistedToken()function ininternal/oauth/config.go:- Checks BBolt storage for persisted tokens
- Returns token status including
hasRefreshTokenandisExpiredflags
-
Added token refresh retry logic in
internal/upstream/core/connection.go:- Before triggering browser OAuth, checks if persisted token has refresh_token
- Retries token refresh with exponential backoff (up to 3 attempts)
- Only falls back to browser OAuth if refresh fails
-
Enhanced logging to distinguish between token scenarios:
- Logs both in-memory and persisted token status
- Logs refresh attempts and outcomes
Run OAuth E2E tests with short token TTL:
go run ./tests/oauthserver/cmd/server -port 9000 -access-token-ttl=30s
# Token should auto-refresh without browser re-authenticationMultiple OAuth flows may run concurrently, causing state corruption:
WARN | ⚠️ OAuth is already in progress, clearing stale state and retrying
INFO | 🧹 Clearing OAuth state | {"was_in_progress": true, "was_completed": false}
The OAuth state machine doesn't properly coordinate multiple reconnection attempts:
- Background reconnection triggers OAuth flow
- Before flow completes, another reconnection attempt starts
- Second attempt clears the first flow's state
- Neither flow completes successfully
-
Implemented
OAuthFlowCoordinatorininternal/oauth/coordinator.go:- Per-server mutex coordination prevents concurrent flows
StartFlow()returnsErrFlowInProgressif flow already activeWaitForFlow()allows goroutines to wait for completionEndFlow()notifies all waiters of completion
-
Integrated coordinator into OAuth strategies:
tryOAuthAuth()andtrySSEOAuthAuth()now use coordinator- Second goroutine waits for first flow instead of starting new one
- Proper cleanup via defer to handle both success and failure
-
Added correlation IDs for flow traceability:
- Each OAuth flow gets a unique UUID
- All related logs include the correlation ID
- Makes debugging concurrent flows much easier
Trigger rapid reconnections and verify single OAuth flow:
# Multiple reconnection attempts should result in single OAuth flow
# Second goroutine should log "waiting for OAuth flow to complete"Browser opening is rate-limited, preventing OAuth flow from completing:
WARN | Browser opening rate limited, skipping
The browser rate limiter prevents opening multiple browser windows in quick succession. However, when combined with Bug 2 (race condition), this can prevent any OAuth flow from completing.
-
Browser rate limiting is already per-server:
- Each
Clientinstance has its ownlastOAuthTimestamp - Rate limiting applies per-server, not globally
- Each
-
Combined with Bug 2 fix:
- With flow coordinator preventing concurrent flows, rate limiting is less problematic
- Only one flow runs at a time per server, so rate limiting works correctly
-
Manual OAuth flows bypass rate limiting:
mcpproxy auth loginsets context flag to bypass rate limit- User-initiated flows always open browser
Browser should open for each unique server's OAuth flow without interference.
internal/oauth/coordinator.go- OAuth flow coordinator for race condition preventioninternal/oauth/correlation.go- Correlation ID generation and context propagationinternal/oauth/logging.go- Enhanced OAuth logging with token redaction
internal/upstream/core/connection.go- Token refresh retry, flow coordinator integrationinternal/oauth/config.go-HasPersistedToken(),GetPersistedRefreshToken()internal/oauth/persistent_token_store.go- Enhanced token metadata logginginternal/oauth/discovery.go- Consistent request/response logging
All fixes are covered by unit tests:
internal/oauth/coordinator_test.gointernal/oauth/correlation_test.gointernal/oauth/logging_test.go
Run verification:
# Unit tests
go test -race ./internal/oauth/... -v
# E2E tests
./scripts/run-oauth-e2e.shinternal/upstream/core/connection.go- OAuth authentication logicinternal/upstream/managed/client.go- Managed client reconnectioninternal/oauth/- OAuth configuration and flow managementtests/oauthserver/- OAuth test server for reproductionspecs/008-oauth-token-refresh/- Feature specification