Supabase's auth client was written for Node and the browser. Cloudflare Workers is neither. Moving session handling to the edge surfaces failures that aren't obvious from the docs — and the error messages don't help. Here's what breaks and the pattern that works.
Why node-assuming auth breaks on Workers
The Workers runtime doesn't have Node's globals. Libraries that reach for Buffer, Node's crypto, or process.env either throw or rely on a shim that behaves differently. Auth code is full of exactly these — JWT verification, base64 handling, secret access.
Two practical consequences:
- Use
@supabase/ssrpatterns designed for server/edge contexts rather than assuming the browser client will work — cookie and storage handling differ. - In Next.js, marking a route as
runtime = 'edge'changes what's available to it; code that ran fine in a Node route can fail once it's edge. Know which runtime each piece of your auth runs in.
The fix throughout is to lean on Web-standard APIs (Web Crypto, Request/Response, the standard fetch) instead of Node-specific ones.
Session and JWT handling at the edge
You often can't reach for jsonwebtoken on Workers. Instead, verify Supabase JWTs using the Web Crypto API directly — import the signing key and verify the token's signature with crypto.subtle. The JWT secret flows in as a Worker secret (not bundled in code).
Refresh-token mechanics need care at the edge: decide deliberately where refresh happens (typically you let the SSR layer manage the refresh/rotation and the Worker validates the access token it's handed), so you don't end up with two components both trying to rotate the same session.
Cookies, CORS, and the redirect dance
This is where cross-origin Worker deployments get fiddly. If your app origin and your Worker are on different domains, cookies need SameSite and Secure set correctly or the browser silently drops them — and "silently" is the operative word, because nothing errors; auth just doesn't stick.
Set cookies explicitly from the Worker's Response headers, get CORS right for credentialed requests (the wildcard origin won't work with credentials), and handle OAuth redirects with care when the provider returns to one domain but your session lives on another. Most "logged in but immediately logged out" bugs trace back to a cookie attribute or a CORS credential mismatch here.
RLS implications when auth runs at the edge
Row Level Security only protects you if the right identity reaches the database. Pass the user's JWT through to Supabase so RLS policies evaluate against that user — don't reach for the service-role key to "make it work," because the service role bypasses RLS entirely and turns a convenience into a security hole.
The ordering bug to watch: if the Worker makes a Supabase call before auth resolves, it either runs unauthenticated or with the wrong context. Make sure identity is established before any data access, and use the anon key + user JWT for user-scoped reads, reserving the service role for genuinely trusted server-only operations.
A working pattern
Putting it together, the shape that works:
- The SSR/middleware layer manages the session and hands the Worker a valid access token (via header or cookie).
- The Worker verifies the JWT with Web Crypto against the Supabase JWT secret.
- For data access, the Worker calls Supabase with the anon key plus the user's JWT, so RLS applies.
- Cookies are set with correct
SameSite/Secure, and CORS is configured for credentialed cross-origin requests. - The service-role key is used only for explicitly trusted, non-user-scoped operations — never as a shortcut around an auth bug.
FAQ
Why does Supabase auth fail on Cloudflare Workers?
The Workers runtime lacks Node globals (Buffer, Node crypto, process). Use @supabase/ssr patterns and verify JWTs with the Web Crypto API.
How do I verify a Supabase JWT without jsonwebtoken?
Use crypto.subtle with the JWT secret imported as a key — the Web Crypto API is available in the Workers runtime.
Why is my user logged in then immediately logged out?
Almost always a cookie attribute (SameSite/Secure) or a CORS credentials mismatch on a cross-origin Worker.