Add automatic browser-based OAuth flow#6
Conversation
Implements a seamless OAuth authentication experience: - Auto-opens browser to authorization URL - Spins up local HTTPS server to capture OAuth callback - Generates self-signed SSL certificate for localhost - Falls back to manual copy/paste flow if automatic fails - Shows success page in browser after authorization This eliminates the need for users to manually copy/paste URLs, making the initial setup much smoother. The implementation follows patterns used by popular CLI tools like gcloud, aws-cli, and gh. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Pull Request Review: Add automatic browser-based OAuth flowThis is a solid enhancement that significantly improves UX. However, there are several important issues to address: Critical Issues to Fix1. URL Construction Bug (line 238) Fix: Use urlparse to construct properly. 2. Race Condition (lines 226-229) Fix: Use threading.Event to signal server is ready before opening browser. 3. Server Resource Leak (lines 169-199) Fix: Add server.server_close() in finally block. 4. Unused Imports (lines 8, 144-145) Security Concerns5. No State Validation (line 17-20) 6. Thread Safety Issue (line 15) Testing Gaps7. No Test Coverage Positive Aspects
VerdictRequest Changes - Great concept and valuable UX improvement, but issues #1, #2, #3, and #6 must be fixed before merging. Add basic test coverage. |
The previous implementation incorrectly concatenated the redirect URI with the callback path, resulting in malformed URLs like: https://localhost:9090/cb/?code=ABC (double slash before query) Now properly uses urlparse/urlunparse to construct valid URLs: - Extracts scheme and netloc from configured redirect_uri - Extracts path and query from callback response - Combines them correctly without double slashes Example: redirect_uri: https://localhost:9090/cb callback: /?code=ABC&state=XYZ result: https://localhost:9090/cb?code=ABC&state=XYZ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The HTTPServer socket was never properly closed, causing resource leaks that could result in "Address already in use" errors on subsequent runs or if the script crashes during authentication. Changes: - Wrapped server operations in try/finally block - Added server.server_close() in finally to ensure socket cleanup - Socket is now properly released even if errors occur during auth flow This prevents port binding issues and ensures clean resource management. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Added threading.Event to ensure the callback server is fully ready before opening the browser. This eliminates the theoretical race condition where the OAuth redirect could arrive before the server is listening. Changes: - _start_callback_server now returns tuple (response, server_ready_event) - Server thread signals ready via event.set() before handling requests - Browser only opens after confirming server is ready - Added 5-second timeout for server startup with fallback to manual flow While the race window was extremely small in practice (microseconds), this makes the flow bulletproof and adds proper error handling for server startup failures. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Cleaned up imports that were not actually used in the code: - parse_qs from urllib.parse (line 8) - tempfile and os from _generate_self_signed_cert (lines 144-145) These were likely left over from earlier iterations of the code. Keeping the codebase clean and removing dead imports. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The class variable authorization_response was shared across all CallbackHandler instances, causing race conditions when multiple OAuth flows run concurrently. Multiple servers would overwrite each other's responses. Changes: - Replaced class variable with queue.Queue for thread-safe communication - Each server instance now gets its own queue - Response is retrieved via queue.get() with timeout - Proper isolation between concurrent OAuth flows This enables safe concurrent authentication to multiple APIs without responses getting mixed up or lost. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Pull Request Review: Add automatic browser-based OAuth flowOverviewThis PR adds an excellent UX improvement by implementing automatic browser-based OAuth flow with graceful fallback. The implementation follows industry-standard patterns used by popular CLI tools. Overall, this is a solid enhancement with good error handling and fallback mechanisms. 🟢 Strengths
🟡 Issues & RecommendationsHigh Priority1. Race Condition in
|
The previous fix still had a race condition: response_queue was a class variable that could be overwritten if multiple OAuth flows ran concurrently. The second flow would overwrite the first's queue, causing responses to be routed to the wrong server. Changes: - Removed global CallbackHandler class - Create BoundCallbackHandler dynamically per-server with queue in closure - Queue is now truly isolated per server instance - Each concurrent OAuth flow gets its own handler class with its own queue This properly isolates concurrent OAuth flows using Python closures instead of shared class variables. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
If the port is already in use (from crashed process, another app, etc), HTTPServer() raises OSError and crashes the OAuth flow. This now catches the error and falls back to manual URL copy/paste flow. Changes: - Wrapped HTTPServer creation in try/except for OSError - Prints helpful error message with port and reason - Returns None to trigger fallback to manual flow - Prevents crash and provides better user experience Common scenarios this fixes: - Port still bound from previous crashed run - Another process using the port - Permissions issues binding to privileged ports 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The previous logic was broken: if redirect_response is None or not server_ready.is_set(): This would ALWAYS take the fallback path because redirect_response is always None immediately after server starts (no callback yet). Browser would never open in automatic mode. Correct flow: 1. Check if server_ready is set (server started successfully) 2. If yes, open browser and wait for response 3. If no response after timeout, fall back to manual 4. If server didn't start, immediately fall back to manual Changes: - Fixed conditional logic to check server_ready first - Only open browser if server started successfully - Separate error messages for "server failed" vs "timeout" - Clearer flow control with nested if/else Now the automatic browser flow actually works! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Pull Request Review: Add automatic browser-based OAuth flowOverviewThis PR adds a significant UX improvement by automating the OAuth flow with browser-based authentication and a local callback server. The implementation follows patterns used by established CLI tools like gcloud and aws-cli. ✅ Strengths
🔴 Critical Issues1. Race Condition in Server Thread (Lines 200-219)Severity: High The current implementation has a potential race condition between setting def run_server():
server_ready.set() # Signal that server is ready
server.handle_request() # Handle one request then stopIssue: The event is set before the server enters its request handling loop. If the browser request arrives before Fix: Move the event signal to after the server is actually listening, or add a small delay. 2. Missing subprocess Import at Module Level (Line 114)Severity: Medium
Fix: Move to top-level imports. 3. Incomplete Error Handling for Token Fetch (Lines 284-289)Severity: Medium The Fix: try:
token = oauth_session.fetch_token(...)
except Exception as e:
print(f"Failed to fetch access token: {e}")
raise
|
- Enable HTTP for localhost OAuth via OAUTHLIB_INSECURE_TRANSPORT - Change default redirect from https://localhost:9090/cb to http://localhost:8080 - Remove all HTTPS certificate generation code (openssl/mkcert) - Force HTTP-only for clean, browser-friendly OAuth flow - Fix timing issues: browser opens after server is ready - Improve callback server architecture for better control - Add enhanced success page with modern UI - Support flexible port configuration via redirect URI This eliminates browser security warnings and provides a smooth OAuth experience. HTTP on localhost is secure per OAuth2 RFC 8252. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Pull Request Review: Add automatic browser-based OAuth flowOverviewThis PR adds a significant UX improvement by automating the OAuth flow with browser-based authentication and a local callback server. The implementation follows industry-standard patterns (similar to gcloud, aws-cli, gh). Overall, this is a well-thought-out enhancement with good fallback mechanisms. ✅ Strengths
🔴 Critical Issues1. Security: Global Environment Variable Side Effect (oauth_session.py:14)Setting os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' globally at module import time affects the entire Python process, not just this library. This could inadvertently enable insecure transport for other OAuth2 operations in the same process. Risk: If this library is imported into a larger application, it could weaken security for other components. Recommendation: Set this only within the relevant function scope, document this clearly as a security consideration, or only enable for localhost URIs. 2. Missing State Validation (oauth_session.py:268)The state parameter is generated but never validated after the callback. OAuth2 best practices require validating the state parameter to prevent CSRF attacks. Risk: Vulnerable to CSRF attacks during the OAuth flow. Recommendation: Store the state and validate it when processing the callback. 3. No Request Timeout on Token Fetch (oauth_session.py:317-321)Network request without timeout could hang indefinitely. Recommendation: Add timeout parameter to fetch_token call.
|
| Issue | Severity | Status |
|---|---|---|
| Global OAUTHLIB_INSECURE_TRANSPORT | High | |
| Missing state validation | High | |
| HTTP allowed for non-localhost | Medium | |
| No timeout on token fetch | Medium |
📝 Recommendations Summary
Before Merge:
- ✅ Fix global environment variable side effect
- ✅ Add OAuth state validation
- ✅ Add timeout to fetch_token call
- ✅ Move import statement to module level
- ✅ Add basic tests for new functionality
Post-Merge:
- Make timeouts configurable
- Add comprehensive test suite
- Consider adding logging framework
- Add integration tests with mock OAuth provider
🎓 Minor Improvements
- Lines 282-283: Remove redundant message when falling back to manual flow
- Line 87: Consider allowing user to configure port via input
- PR Description: The mention of self-signed SSL certificates in the PR description doesn't match the implementation (uses HTTP, not HTTPS)
🏁 Verdict
Conditional Approval: This is a valuable feature with good implementation quality. However, the security issues (global environment variable and missing state validation) should be addressed before merging to production.
The code demonstrates good software engineering practices with fallback mechanisms and error handling. With the recommended security fixes and basic tests, this will be a solid addition to the codebase.
Great work on improving the user experience! The automatic browser flow is much more user-friendly than manual URL copy-pasting. 🚀
Review generated by Claude Code
Change default from http://localhost:8080 to http://localhost:8080/cb for better OAuth convention alignment. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Pull Request Review: Add automatic browser-based OAuth flowSummaryThis PR adds a significant UX improvement by implementing automatic browser-based OAuth flow with a local callback server. The implementation follows industry-standard patterns used by CLI tools. Overall, the code is well-structured with proper error handling and graceful fallbacks. However, there are several security concerns, potential bugs, and improvements to consider. Critical Issues1. Security: Global Environment Variable Modification (Line 14)Problem: Setting OAUTHLIB_INSECURE_TRANSPORT globally at module import time affects all OAuth sessions in the process, not just this library. This could weaken security for other OAuth operations. Impact: If this library is imported by a larger application, it inadvertently enables insecure transport for all OAuth operations across the entire process. Recommendation:
2. Security: HTTP Default for Redirect URI (Line 91)Problem: Defaults to HTTP instead of HTTPS. While localhost traffic does not leave the machine, some OAuth providers reject HTTP redirect URIs, and it sets a poor security example. Impact:
Recommendation: Consider defaulting to HTTPS and implementing proper certificate handling as mentioned in the PR description. Significant Issues3. Resource Management: Server Cleanup on Early Return (Lines 279-284)Problem: When returning None from _start_callback_server() at line 224, the server_ready event is never set, but we still return it. The calling code then checks both conditions, but the thread might still be running. Recommendation: Ensure clean shutdown of any started threads before returning None. 4. Race Condition: Server Ready Check (Lines 239-243)Problem: If the server takes slightly longer than 5 seconds to start (e.g., under heavy load), it is treated as a failure even though it might start successfully shortly after. Impact: Unnecessary fallback to manual flow on slower systems. Recommendation: Consider increasing timeout or making it configurable. 5. Error Handling: Missing State Validation (Line 268)Problem: The state parameter is generated but never validated in the callback. OAuth2 state parameters are critical for CSRF protection. Impact: Potential security vulnerability to CSRF attacks in the OAuth flow. Recommendation: Store the state and validate it against the callback response. Minor Issues & Improvements6. Code Style: Inconsistent Spacing (Lines 16-17)Issue: Three blank lines instead of the PEP 8 recommended two. 7. Import Organization (Line 246)Issue: Import statement (import time) in the middle of a function. Should be at module level. 8. Error Handling: Generic Exception Catch (Line 251)Issue: Catches all exceptions but does not log the error before re-raising. 9. Timeout Hardcoded (Line 293)Issue: 2-minute timeout is hardcoded. Different users/scenarios might need different timeouts. 10. Missing Error Context in Fallback (Lines 310-314)Issue: User does not know how long they waited. Consider telling them Waited 2 minutes for clarity. Test Coverage ConcernsThe PR description includes a test plan, but no automated tests are included:
Recommendation: Add pytest tests covering:
Positive Aspects
Recommendations SummaryMust Fix:
Should Fix:
Nice to Have:
Overall AssessmentThis is a solid implementation that significantly improves the user experience. The code demonstrates good understanding of OAuth flows, threading, and error handling. However, the security concerns around the global environment variable and missing state validation need to be addressed before merging. Recommendation: Request changes for security issues, then approve after fixes. |
There was a problem hiding this comment.
Pull Request Overview
This PR implements an automatic browser-based OAuth flow that improves the user experience by eliminating manual URL copy/pasting. The implementation includes automatic browser opening, a local HTTP server for callback handling, and graceful fallback to manual flow when needed.
Key Changes:
- Adds automatic browser opening and local callback server for OAuth flow
- Changes default redirect URI from HTTPS (port 9090) to HTTP (port 8080)
- Implements timeout-based fallback to manual authorization flow
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
|
||
| # Allow OAuth2 over HTTP for localhost (development only) | ||
| # This is safe because localhost traffic never leaves your machine | ||
| os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' |
There was a problem hiding this comment.
Setting OAUTHLIB_INSECURE_TRANSPORT globally affects all OAuth sessions in the process. Consider setting this only for localhost redirect URIs to prevent accidentally allowing insecure transport for production OAuth endpoints. You can check if the redirect_uri is localhost before setting this environment variable.
| def _prompt_for_config(self) -> Dict: | ||
| """Prompt user for OAuth2 configuration and save it.""" | ||
| print("No config saved to file yet, lets get set up!") | ||
| print("\nFor the redirect URL, use http://localhost:8080/cb (recommended for OAuth)") |
There was a problem hiding this comment.
Corrected capitalization of 'URL' in user-facing message.
|
|
||
| def _start_callback_server(self, host: str, port: int) -> tuple[Optional[HTTPServer], queue.Queue, threading.Event]: | ||
| """Start a local server to capture OAuth callback. | ||
|
|
There was a problem hiding this comment.
The docstring doesn't document the host and port parameters. Add parameter descriptions to clarify their purpose and expected values.
| Args: | |
| host (str): Hostname or IP address for the local callback server to listen on (e.g., "localhost"). | |
| port (int): Port number for the local callback server (e.g., 8080). |
| if not server_ready.wait(timeout=5): | ||
| print("Warning: Server failed to start, falling back to manual flow") | ||
| server.server_close() | ||
| return None, response_queue, server_ready |
There was a problem hiding this comment.
If server_ready.wait() times out, the server thread may still be running. The thread is marked as daemon so it won't block process exit, but server.server_close() is called before the thread has confirmed startup. This could cause issues if the thread is still initializing. Consider adding a flag to signal the thread to stop or waiting briefly for the thread to complete.
| import time | ||
| time.sleep(0.1) |
There was a problem hiding this comment.
The time module should be imported at the top of the file with other imports rather than inline within a function. This follows Python conventions and improves code readability.
|
|
||
| # Wait for callback from queue (with timeout) | ||
| try: | ||
| callback_path = response_queue.get(timeout=120) |
There was a problem hiding this comment.
The timeout value of 120 seconds is a magic number. Consider defining it as a named constant (e.g., OAUTH_CALLBACK_TIMEOUT_SECONDS = 120) at the class or module level to improve maintainability and make it easier to adjust.
| server, response_queue, server_ready = self._start_callback_server(host, port) | ||
|
|
||
| # Check if server started successfully | ||
| if server is None or not server_ready.is_set(): |
There was a problem hiding this comment.
The condition 'not server_ready.is_set()' is redundant here. If the server is None (line 224), server_ready.set() is never called, so it will never be set. The second condition will always be True when server is None. Consider simplifying to just 'if server is None:' for clarity.
| if server is None or not server_ready.is_set(): | |
| if server is None: |
| redirect_response = urlunparse(( | ||
| parsed_redirect.scheme, | ||
| parsed_redirect.netloc, | ||
| parsed_callback.path or parsed_redirect.path, | ||
| '', # params | ||
| parsed_callback.query, | ||
| '' # fragment | ||
| )) |
There was a problem hiding this comment.
The logic for reconstructing the redirect URL could benefit from a comment explaining why we're combining components from both parsed_redirect and parsed_callback, particularly the fallback behavior for the path on line 304.
|
I think we're done here. This is just meant for utility scripts, not a production environment. |
Summary
Implements a seamless OAuth authentication experience that eliminates manual URL copy/pasting:
Benefits
Technical Details
http.serverandthreadingmodules.api-blaster/directoryTest Plan
🤖 Generated with Claude Code