Skip to content

fix: add has trap to lazy auth proxy so auth routes resolve handler#139

Merged
ephraimduncan merged 2 commits into
mainfrom
fix/auth-proxy-has-trap
Jun 1, 2026
Merged

fix: add has trap to lazy auth proxy so auth routes resolve handler#139
ephraimduncan merged 2 commits into
mainfrom
fix/auth-proxy-has-trap

Conversation

@ephraimduncan
Copy link
Copy Markdown
Contributor

Problem

In production (Cloudflare Workers), every POST /api/auth/* returned 500 with TypeError: e8 is not a function, breaking sign-in (observed on /api/auth/sign-in/social).

Root cause

packages/auth/index.ts exposes auth as a lazy new Proxy({}, { get }) to defer createAuth() past build-time env validation. It trapped only getnot has.

better-auth's toNextJsHandler runs this per request:

return "handler" in auth ? auth.handler(request) : auth(request);

With no has trap, "handler" in auth forwards to the empty proxy target {}false, so better-auth falls through to auth(request). The proxy isn't callable → TypeError: auth is not a function (minified to e8).

This breaks all /api/auth/* HTTP routes (social and email). Server-side auth.api.getSession(...) was unaffected because it goes through the get trap — which is why page session checks kept working and only the sign-in POSTs 500'd.

Fix

Add a has trap that forwards to the real instance:

has(_target, property) {
  return Reflect.has(getAuth(), property);
},

Build-time safe: toNextJsHandler only evaluates "handler" in auth inside its per-request closure, never at module load, so the env-validation decoupling is preserved.

Verification

Reproduced against the actual published better-auth@1.4.10 + toNextJsHandler, posting to /api/auth/sign-in/social:

  • Before (get-only proxy): TypeError: auth is not a function — matches prod e8.
  • After (with has trap): 200 with {"url":"https://github.com/login/oauth/authorize?..."}.

Note

Deploying this requires a Worker rebuild + redeploy to ship the new bundle. No env/secret/DNS changes needed.

The lazy `auth` Proxy only trapped `get`, leaving `has` to forward to its
empty target. better-auth's `toNextJsHandler` checks `"handler" in auth`
per request; that returned false, so it fell through to calling
`auth(request)`, which isn't callable. Result: every /api/auth/* POST threw
"TypeError: ... is not a function" in production (e.g. sign-in/social).

Add a `has` trap forwarding to the real instance. Stays build-time safe: the
check only runs inside toNextJsHandler's per-request closure, never at module
load, so env-validation decoupling is preserved.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Jun 1, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
formbase-web 6b8331f Jun 01 2026, 10:02 AM

The get-traps assigned `Reflect.get(...)`'s `any` return, tripping
@typescript-eslint/no-unsafe-{assignment,return,call,member-access} (12
errors, pre-existing red on main since the proxies landed). Annotate the
forwarded value as `unknown` and cast on the function branch so binding is
type-checked. Runtime behavior is unchanged.
@ephraimduncan ephraimduncan merged commit 68bd764 into main Jun 1, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant