-
-
Notifications
You must be signed in to change notification settings - Fork 185
Description
Describe the bug
When using createServerClient
from @supabase/ssr
on the server-side (Node.js), the internally initialized GoTrueClient
calls _initialize()
in its constructor. This method attempts to access window.location.href
, which throws a ReferenceError
in non-browser environments and causes the application to crash.
While the method recoverAndRefresh()
is designed to work in server-side environments (it can load session information from cookies or other configured storage), _initialize()
assumes a browser environment and throws an exception before recoverAndRefresh()
is ever reached.
In practice, other methods may recover the session later, so this doesn’t immediately break all functionality. However, this still represents an inconsistent design: recoverAndRefresh()
is server-compatible, yet it's gated behind a browser-only execution path. The fact that isBrowser()
is checked later inside the same method also implies that this code was intended to run in both environments.
To Reproduce
I've created a reproducible example with Express. You can check the whole code here. This is not a bug specific to Express; the crash stems from the way @supabase/auth-js is structured internally.
1. Install Supabase
npm install @supabase/ssr
Note: This issue is in @supabase/auth-js, not @supabase/ssr. However, I recommend using @supabase/ssr to simplify cookie-based session storage configuration on the server.
2. Enable debug logging in _initialize()
In @supabase/auth-js/src/GoTrueClient.ts, modify the catch block inside _initialize() around line 383 to add this._debug() to observe the crash:
await this._recoverAndRefresh()
return { error: null }
} catch (error) {`
this._debug('#_initialize()', 'error', error) // add this
if (isAuthError(error)) {
return { error }
}
3. Minimal Express route to reproduce
const express = require("express");
const { createServerClient } = require("@supabase/ssr");
const router = express.Router();
router.get("/issue", async (req, res) => {
const SUPABASE_PUBLIC_URL = process.env.SUPABASE_PUBLIC_URL;
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY;
if (!SUPABASE_PUBLIC_URL || !SUPABASE_ANON_KEY) {
return res.status(500).json({ error: "Missing environment variables" });
}
const supabase = createServerClient(
SUPABASE_PUBLIC_URL,
SUPABASE_ANON_KEY,
{
auth: {
debug: true, // important for tracing the issue
},
cookieOptions: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
},
cookies: {
getAll() {
return Object.entries(req.cookies).map(([name, value]) => ({
name,
value: value || "",
}));
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
res.cookie(name, value, options);
});
},
},
cookieEncoding: "base64url",
}
);
res.json({ success: true, message: "Supabase initialized" });
});
module.exports = router;
4. Send a request
Request the /issue
route. You’ll observe that _initialize()
throws a ReferenceError: window is not defined
.
Expected behavior
In Node.js environments, _initialize() should not attempt to access window.location.href. It should conditionally access that property only when isBrowser() is true. This would allow the server-side recoverAndRefresh() logic to work as intended.
Screenshots
If applicable, add screenshots to help explain your problem.
System information
- OS: Microsoft Windows 11 Home
- Runtime: Node.js v22.13.1
- Packages:
{
"@supabase/ssr": "^0.6.1",
"@supabase/supabase-js": "^2.50.0",
"cookie-parser": "^1.4.7",
"dotenv": "^16.5.0",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2"
}
Additional context
none.