Engineering Journal
Ginexys
Ginexys

Stateless Edge Auth: PKCE OAuth, HttpOnly Cookies, and a VS Code Handshake on Cloudflare Functions

2026-06-03

TLDR

When building web developer tools on a zero-persistent-server architecture, we had to manage auth across three distinct surfaces: a browser modal, a standalone landing page, and a native VS Code webview extension. By pairing stateless Cloudflare Functions with Supabase as an external identity provider, we enforced short-lived JWTs, HttpOnly cookie storage for web sessions, OS Keychain storage for the editor extension, and a polling handshake with temporary AES-GCM encryption.
SurfaceSession Storage MechanismAuth Protocol / PatternKey Expiration & Security Boundary
Browser ModalHttpOnly gx_session CookiePKCE OAuth + BroadcastChannel1-Hour JWT max; 600ms transient localStorage handoff
Standalone PageHttpOnly gx_session CookieDirect OAuth / Password grantRefresh token stripped; valid only while tab is active
VS Code ExtensionOS Keychain (context.secrets)Polling Handshake via AES-GCMEncrypted via ephemeral HMAC key; auto-purged on delivery

Zero-server architectures require edge-based auth boundaries

When we set out to build our developer tool suite, we ran straight into a strict architectural constraint: zero persistent backend servers. We did not want a database cluster sitting around statefully tracking user session tokens on every HTTP request. Everything had to run inside stateless edge workers (Cloudflare Functions) paired with Supabase as an external identity provider.

This constraint immediately eliminated standard auth patterns. We could not maintain server-side session IDs in a database, nor could we run server-side refresh token rotation with real-time revocation lists. Every single session had to rely either on stateless JWT verification or an external ephemeral store.

The main trade-off was capping session lifetimes at the JWT expiration limit, exactly one hour. For a developer tool, that is completely reasonable, but orchestrating the handoff smoothly across browser modals, standalone tabs, and native VS Code webview panels took careful wiring.


Multi-surface client environments introduce token exposure and state loss

Supporting three separate client surfaces created distinct edge cases that could easily compromise session security if handled carelessly:

  1. Browser modal state collisions: Standard OAuth redirects reload the active page, which would completely destroy the developer's in-memory workbench state (such as unsaved canvas regions or active table edits).
  2. Refresh token exposure: Storing long-lived refresh tokens in browser localStorage leaves the system vulnerable to token exfiltration if any third-party script or XSS payload manages to execute.
  3. VS Code webview isolation: Native VS Code webview panels cannot accept standard HTTP redirects from OAuth providers without breaking the internal extension host state.

Decoupled edge handlers isolate credentials by client platform

We solved these issues by creating explicit boundaries for each surface while sharing a single stateless edge endpoint:

Browser modal and ephemeral handoff

For password sign-ins, signInWithPassword() returns a session. The modal sends a POST to /api/auth/session containing { action: 'set', access_token, expires_in } to establish the HttpOnly cookie, immediately purging Supabase keys from localStorage. The token sits in localStorage for roughly 600ms during handoff.

For OAuth, we pass skipBrowserRedirect: true to open the provider in a popup window, preserving the main tab's state. Once authenticated, the popup sends a gx:auth-complete event via BroadcastChannel to the main window.

Main Window             OAuth Popup Window          OAuth Provider         CF Edge Function
    |                            |                        |                        |
    |--- 1. Open popup --------->|                        |                        |
    |    (skipBrowserRedirect)   |                        |                        |
    |                            |--- 2. Authenticate --->|                        |
    |                            |<-- 3. Access token ----|                        |
    |<-- 4. Broadcast -----------|                        |                        |
    |    "gx:auth-complete"      |                        |                        |
    |                            |--- 5. Close popup ---->| (close)                |
    |--- 6. POST /session (token) ------------------------------------------------>|
    |<-- 7. Set-Cookie: gx_session (HttpOnly) -------------------------------------|
    |--- 8. Evict localStorage --|                        |                        |
async function handleOAuthReturn() {
  if (!oauthPending) return;
  oauthPending = false;
  const token = readStoredToken(); // Reads transient Supabase localStorage
  if (!token) return;

// Evict immediately: HttpOnly cookie is the true session for (let i = localStorage.length - 1; i >= 0; i--) { const k = localStorage.key(i); if (k?.startsWith('sb-') && k.endsWith('-auth-token')) localStorage.removeItem(k); } await onSignInSuccess(token); }

Standalone login page

For direct logins, we strip the refresh_token field from the session object before persisting anything. If an access token is compromised, it naturally expires in one hour; by refusing to store the refresh token on the client, we ensure a stolen credential cannot grant indefinite access.

VS Code extension polling handshake

To bridge VS Code and the web browser securely, we built a zero-knowledge polling protocol:
  1. The extension generates a unique state UUID and a 32-byte hex secret.
  2. The extension sends a POST to /auth/vscode with Authorization: Bearer <secret>. The edge worker creates a temporary session row with a 5-minute TTL storing the hashed secret.
  3. The extension opens the browser login page with ?state=<state>&from=vscode.
  4. Upon successful login, the web page encrypts the access token using AES-GCM with a key derived from HMAC-SHA256(SERVICE_ROLE_KEY, state) and writes it to the session row.
  5. The extension polls /auth/vscode-poll?state=<state> with its bearer secret every 5 seconds.
  6. The edge function decrypts the payload, returns the token, and deletes the row instantly.
  7. The extension saves the token inside the OS keychain via context.secrets.store().
VS Code Extension           Cloudflare Edge Worker           Web Browser Login Page
        |                               |                               |
        |--- 1. POST /auth/vscode ----->|                               |
        |    (Bearer secret)            |                               |
        |                               |--- Store hashed secret -------|
        |                               |    (5-min TTL)                |
        |--- 2. Open login page (?state=UUID) ------------------------->|
        |                               |<-- 3. Auth & write GCM token -|
        |                               |                               |
        |=== [Loop Every 5 seconds] ====================================|
        |                               |                               |
        |--- 4. GET /auth/vscode-poll ->|                               |
        |    (state, secret)            |                               |
        |===============================================================|
        |                               |                               |
        |<-- 5. Decrypted access token -|                               |
        |    & delete row               |                               |
        |--- 6. Save to OS Keychain ----|                               |

Server-side edge endpoints enforce HttpOnly scopes and PKCE validation

All web surfaces converge on a single edge function endpoint that sets the gx_session cookie:
// Set Session
const cookie = gx_session=${token}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=${maxAge};

// Clear Session const cookie = gx_session=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0;

Every auth request also explicitly enforces flowType: 'pkce'. Following RFC 7636, the client generates a code_verifier and hashes it into a code_challenge. Even if an attacker intercepts the authorization code in flight, it is useless without the verifier residing inside browser memory.

Rule of thumb: Never rely on client-side cleanup for HttpOnly cookies. Because JavaScript cannot read HttpOnly headers, explicit logouts must always execute a server-side roundtrip with Max-Age=0 to invalidate the session cookie.
Read this post in the full Engineering Journal →