Wrangler WebSocket Auth Relay #13117
petebacondarwin
started this conversation in
RFCs
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
The current
wrangler loginflow requires Wrangler to host a local HTTP server onlocalhost:8976to receive the OAuth callback. This doesn't work well in containers, remote VMs, Codespaces, or other environments where localhost isn't accessible from the user's browser.Solution Overview
Introduce a Cloudflare Worker relay (
auth.devprod.cloudflare.dev) with a Durable Object that acts as a WebSocket bridge between Wrangler (in any environment) and the OAuth callback (from the user's browser).Flow
Security Model
stateparam (32-char crypto-random, generated by Wrangler the same way it is today) identifies the DO instance and provides CSRF protection. Wrangler validates the returned state matches what it generated.code_verifier) can exchange the code for a token. Thecode_verifiernever leaves Wrangler.Part 1: New Package —
packages/wrangler-auth-worker/File Structure
src/index.ts— Worker Entry PointRoutes:
GET /session/:state(withUpgrade: websocketheader)env.AUTH_SESSION.idFromName(state)GET /callback?code=X&state=Ystate, route to DO, return 307 redirectCallback handling mirrors the current Wrangler local server behavior (no HTML pages, only 307 redirects):
?error=access_denied&state=Y: route to DO (sends error via WebSocket), return307redirect tohttps://welcome.developers.workers.dev/wrangler-oauth-consent-denied?code=X&state=Y: route to DO (sends code via WebSocket), return307redirect tohttps://welcome.developers.workers.dev/wrangler-oauth-consent-grantedsrc/auth-session.ts— AuthSession Durable ObjectUses the Hibernation API for efficient WebSocket handling.
Key behaviors:
wrangler.jsonc
package.json
{ "name": "@cloudflare/wrangler-auth-worker", "private": true, "workers-sdk": { "prerelease": true, "deploy": true }, "devDependencies": { "wrangler": "workspace:*", "@cloudflare/workers-types": "catalog:default", "@cloudflare/workers-tsconfig": "workspace:*", "@cloudflare/eslint-config-shared": "workspace:*", "@cloudflare/vitest-pool-workers": "workspace:*", "eslint": "catalog:default", "typescript": "catalog:default", "vitest": "catalog:vitest-3" }, "scripts": { "deploy": "wrangler deploy" } }Part 2: Wrangler Changes
2.1 New CLI Flag —
packages/wrangler/src/user/commands.tsAdd
--x-websocket-callbackflag to thelogincommand args (hidden, experimental):Pass it through to
login(). Eventually this becomes the default and the flag is removed.2.2 Auth Worker URL —
packages/wrangler/src/user/auth-variables.tsAdd a new configurable URL for the auth worker. A single deployment serves all environments (production and staging) since the worker only holds ephemeral relay state and is fully agnostic to the OAuth environment. FedRAMP is not a concern because OAuth login is already rejected entirely for
fedramp_highusers (they must use API tokens).2.3 Parameterize redirect_uri —
packages/wrangler/src/user/generate-auth-url.tsCurrently
redirect_uriis hardcoded toOAUTH_CALLBACK_URL(http://localhost:8976/oauth/callback). Make it a parameter with a backward-compatible default:2.4 WebSocket Auth Flow —
packages/wrangler/src/user/user.tsAdd a new function
getOauthTokenViaWebSocket()as an alternative togetOauthToken().Pseudocode (~60 lines):
2.5 Wire Into
login()—packages/wrangler/src/user/user.ts(~line 1136)Branch based on
--x-websocket-callback:Part 3: Files Summary
packages/wrangler-auth-worker/package.jsondeploy: truepackages/wrangler-auth-worker/wrangler.jsoncpackages/wrangler-auth-worker/tsconfig.jsonpackages/wrangler-auth-worker/eslint.config.mjspackages/wrangler-auth-worker/src/index.tspackages/wrangler-auth-worker/src/auth-session.tspackages/wrangler-auth-worker/tests/vitest.config.mtspackages/wrangler-auth-worker/tests/auth-session.test.tspackages/wrangler/src/user/commands.ts--x-websocket-callbackflagpackages/wrangler/src/user/auth-variables.tsgetAuthWorkerUrlFromEnv()packages/wrangler/src/user/generate-auth-url.tsredirect_uriviacallbackUrlpackages/wrangler/src/user/user.tsgetOauthTokenViaWebSocket(), branch inlogin()Part 4: Prerequisites
OAuth Redirect URI Registration (BLOCKING)
https://auth.devprod.cloudflare.dev/callbackmust be registered as a validredirect_urifor Wrangler's OAuth client IDs on Cloudflare's OAuth server atdash.cloudflare.com:54d11594-84e4-41aa-b438-e81b8fa78ee74b2ea6cc-9421-4761-874b-ce550e0e3defBoth clients use the same auth worker URL — there is no separate staging auth worker, since the worker only relays ephemeral auth codes and is agnostic to the OAuth environment.
FedRAMP does not need consideration here — OAuth login is already rejected for
fedramp_highat the Wrangler CLI level (users must use API tokens instead).This requires coordination with the team managing Cloudflare's OAuth server configuration.
DNS / Zone Setup
The
auth.devprod.cloudflare.devsubdomain needs to be configured. Thedevprod.cloudflare.devzone is already in use by other workers in this repo (e.g.,playground.devprod.cloudflare.dev), so the zone should already be available.WebSocket Client in Wrangler
Node.js has a global
WebSocketavailable since v21. Since this project requires Node >= 20, we should verify availability and potentially use thewsnpm package as a fallback or polyfill. Alternatively, we could useundici's WebSocket support.Part 5: Future Work (not in scope for initial PR)
--x-websocket-callbackflag, use WebSocket flow by default, keep--local-serveras fallback for air-gapped environments.Reference
Current Wrangler Auth Flow
For context, the current
wrangler loginflow works as follows:code_verifier+code_challenge) and a randomstatelocalhost:8976dash.cloudflare.com/oauth2/authwithredirect_uri=http://localhost:8976/oauth/callbackhttp://localhost:8976/oauth/callback?code=X&state=Ystate, exchangescodefor tokens usingcode_verifier307redirect towelcome.developers.workers.dev/wrangler-oauth-consent-granted~/.wrangler/config/default.tomlKey files in the current implementation:
packages/wrangler/src/user/user.tspackages/wrangler/src/user/commands.tspackages/wrangler/src/user/auth-variables.tspackages/wrangler/src/user/generate-auth-url.tsOAUTH_CALLBACK_URLconstantpackages/wrangler/src/user/generate-random-state.tsPrior Art: OpenCode OAuth Patterns
OpenCode (Anthropic's CLI for Claude) faces the same OAuth challenge — authenticating users from terminal environments where localhost may not be accessible. It uses two complementary strategies across its provider and MCP server authentication.
Device Authorization Flow (no localhost required)
For GitHub Copilot and OpenAI Codex "headless" mode, OpenCode avoids the localhost problem entirely by using the Device Authorization Flow (RFC 8628):
device_code+user_codeslow_downresponses per RFC 8628 §3.5 by increasing the poll intervalThis works in containers, remote VMs, and Codespaces because the browser never needs to redirect back to the CLI — the CLI polls for completion instead.
https://github.com/login/device/codehttps://github.com/login/oauth/access_tokenOv23li8tweQw6odWQebzhttps://auth.openai.com/api/accounts/deviceauth/starthttps://auth.openai.com/oauth/tokenapp_EMoamEEZ73f0CkXaXp7hrannTradeoff vs. WebSocket relay: Device flow requires no server infrastructure but depends on the OAuth provider supporting it. Cloudflare's OAuth server does not currently support device flow, which is why this design uses the WebSocket relay approach instead.
Local Callback Server Patterns
When device flow isn't available, OpenCode uses local HTTP callback servers — similar to Wrangler's current approach:
/auth/callback/mcp/oauth/callbackBoth use PKCE (S256) and state parameter validation for security. The MCP callback server additionally supports multiple concurrent pending auths keyed by state, which allows parallel authentication to different MCP servers.
Relevant similarities to Wrangler's current flow:
Key difference: OpenCode does not currently have a relay mechanism for environments where localhost is inaccessible. For MCP servers, the fallback is that the user must configure an API token manually. This is the gap the WebSocket relay design addresses for Wrangler.
OAuth 2.0 Device Authorization Grant approach
The OAuth 2.0 Device Authorization Grant (often just called the "Device Flow") is an authentication method designed specifically for devices that have an internet connection but either lack a web browser or have very limited input capabilities. Think of Smart TVs, game consoles, CLI (command-line interface) tools, or IoT devices where typing out a complex password with a remote control is a frustrating experience.
Instead of forcing the user to log in directly on the device, the Device Flow offloads the authentication to a secondary device, like a smartphone or a laptop.
How It Works
The process involves three main actors: the Device (the smart TV or CLI), the Authorization Server (the identity provider, like Google or Okta), and the User (with their smartphone or laptop).
1. The Device Requests Authorization When the user wants to log in, the device sends a request to the Authorization Server's device endpoint. It asks for permission to start the login process.
2. The Authorization Server Responds with Codes The server responds to the device with a few critical pieces of information:
device_code: A long, secure string that the device keeps secret to identify its request.user_code: A short, easy-to-type string (e.g.,ABCD-1234).verification_uri: A URL for the user to visit (e.g.,login.example.com/device).expires_in: How long these codes are valid (usually 10–15 minutes).interval: How often the device is allowed to poll the server for an update.3. The Device Prompts the User The device displays a message to the user: "Please go to
login.example.com/deviceon your phone and enter the codeABCD-1234."4. The User Authenticates (On a Secondary Device) The user pulls out their smartphone, navigates to the URL, and enters the
user_code. They are then prompted to log into their account normally (using passwords, biometrics, 2FA, etc.) and explicitly grant the device access to their account.5. The Device Polls for a Token (Background Process) While the user is fumbling with their phone in Step 4, the device is not just sitting idle. It continuously "polls" the Authorization Server's token endpoint in the background (e.g., every 5 seconds).
device_codeand asks: "Did the user authorize me yet?"authorization_pending(meaning, "Not yet, keep waiting").6. Access Granted Once the user finishes logging in and granting permission on their phone, the device's next polling request will finally succeed. The Authorization Server stops saying "pending" and instead returns an
access_token(and optionally arefresh_token). The device uses this token to access the user's data, and the screen updates to show that the user is successfully logged in.Beta Was this translation helpful? Give feedback.
All reactions