Your Login Check Is Lying.
Your app has authentication. Users log in. Protected pages redirect to the login screen. The auth flow works perfectly in every test.
But "the auth flow works" and "the auth is actually enforced" are not the same thing.
In many AI-generated apps, the auth check happens in the wrong place, trusts the wrong data, or protects the wrong layer. The login page exists. The redirect works. But the API endpoints behind the protected pages serve data to anyone who asks — because the server never independently verifies who is making the request.
The most common pattern: the app uses getSession() on the server to check if a user is logged in. The session data comes from a cookie. The cookie is not verified against the auth server. An attacker who modifies the cookie can impersonate any user. Supabase themselves changed their recommendation and added a console warning: getUser() verifies the token server-side; getSession() does not.
This is not a theoretical gap. AI tools trained on older documentation and code examples consistently generate the insecure pattern. The app passes every functional test, because the tests are run by honest users with valid sessions.
Who This Is For
- Founders whose app has protected pages or premium features gated by login
- Developers who use Supabase auth in a Next.js application and aren't sure which auth function they're using on the server
- Teams that rely on Next.js middleware for route protection
- Anyone whose auth "works in testing" but hasn't been tested by someone trying to bypass it
If your server-side code trusts session data from a cookie without verifying it against the auth provider, your authentication is a statement of intent, not a security boundary.
What Founders Experience
- Auth looks correct. The login page works. Protected pages redirect unauthenticated users. Role-based UI shows the right elements to the right users. Every manual test passes.
- Nobody tests the bypass. The AI generated auth for legitimate users. Nobody opens DevTools and modifies the session cookie. Nobody calls API endpoints directly without a valid session. Nobody sends requests with a manipulated token.
- The false confidence is total. The founder says "we have auth" with complete confidence. The statement is technically true — the app has authentication UI. But the server doesn't independently verify who is making requests.
- A security review reveals the gap. An auditor, a security researcher, or a new developer looks at the code and finds: the server reads session data from a cookie, uses it for access decisions, and never verifies it with the auth provider. The entire auth system is trusting the client.
What's Actually Happening
Authentication in a web app has two layers: verifying identity (who is this user?) and enforcing access (what can this user do?). AI-generated apps frequently implement the first and skip the second — or implement both in the client only.
Three patterns create this false safety:
1. getSession() on the Server Instead of getUser()
This is the most widespread auth vulnerability in Supabase + Next.js applications.
getSession() reads the user's session from the browser cookie. It returns the user's ID, email, and metadata — fast and convenient. But it does not verify the session token against Supabase's auth server. This makes it untrusted for server-side access decisions — it should not be used as the source of truth for sensitive authorization logic.
getUser() sends the token to Supabase's auth server for verification. It confirms the token is valid, unexpired, and belongs to a real user. It's slower, but it's the correct choice for any server-side security boundary.
Supabase changed their recommendation and added a runtime console warning when getSession() is used in server-side contexts. But AI tools trained on pre-change documentation and code examples continue generating getSession(). One Supabase GitHub discussion described the risk: "An attacker can easily sign up for an account on your app, then tweak their own cookie value to have session.user.id be the victim's user id."
2. Auth in Layout or Middleware Only — Not in Route Handlers
Next.js middleware can check authentication before a page loads. Layout components can redirect unauthenticated users. Both of these create the appearance of protection.
But middleware alone is not a reliable security boundary. Route handlers and API endpoints must still verify auth independently — middleware adds a defense layer, but it cannot be the only one. If /api/user/data doesn't independently verify the session, a direct API call bypasses the page-level protection.
CVE-2025-29927 (CVSS 9.1) demonstrated why this matters: by adding a specific HTTP header (x-middleware-subrequest), all Next.js middleware could be skipped entirely. Every route that relied solely on middleware for auth was exposed.
3. Client-Side Role Checks Without Server Enforcement
The AI generates a useUser() hook that reads the user's role and conditionally renders UI elements. Admin buttons are hidden from non-admin users. Premium features are hidden from free users.
But the API endpoints that serve admin data or premium content have no role check. The UI hides the button; the server serves the data to anyone who asks. This is the same pattern as the admin protection problem, but it applies to any role-based feature — not just admin panels.
What This Puts at Risk
User impersonation. If session data is trusted from the cookie without verification, an attacker can impersonate any user by modifying their own session cookie. They gain access to that user's data, settings, and capabilities.
Data access bypass. Protected pages that rely on middleware or layout-level auth checks can be bypassed by calling the underlying API endpoints directly. Everything the page shows is accessible without the page's auth layer.
Session persistence after revocation. Supabase JWTs have a default expiry of one hour. signOut() deletes the session from the database but does not invalidate already-issued JWTs. A compromised account can continue making authenticated requests for up to 60 minutes after logout.
False security posture. The founder reports "we have authentication" with complete confidence. Security questionnaires are answered affirmatively. But the authentication is enforced at the UI layer, not the server layer — and any technical evaluation will find the gap.
How Trust Score Detects It
Trust Score checks three patterns that catch false auth confidence:
AUTH-13: getUser() not getSession() for server-side auth. Scans server-side code for getSession() usage in auth decision paths. This catches the most common Supabase auth vulnerability: trusting unverified session cookies for access control.
AUTH-06: Protected routes redirect unauthenticated users. Checks that protected pages return appropriate responses (redirect or 401) to unauthenticated requests — not just at the middleware level, but at the route handler level.
ADM-03: No client-side-only role checks. Detects patterns where role-based access decisions happen only in the UI layer (React components, hooks) without corresponding server-side enforcement on the API endpoints.
Real Incidents
getSession() cookie impersonation (Supabase GitHub). A Supabase community member described the vulnerability: "An attacker can easily sign up for an account on your app, then tweak their own cookie value to have session.user.id be the victim's user id." Supabase added a console warning for server-side getSession() usage.
CVE-2025-29927 — Next.js middleware bypass (March 2025). A critical vulnerability (CVSS 9.1) allowed all Next.js middleware to be bypassed by adding the header x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware. Every middleware-protected route was exposed. Affected Next.js versions 11.1.4 through 15.2.2.
Cursor-built app — missing auth on /api/users/:id for 6 weeks. A developer ran a security scan on their Cursor-built app and found: SQL injection in the search endpoint, hardcoded JWT secret, and missing auth on the user data endpoint. Any user could access any other user's data by changing the ID parameter. The missing auth was live for 6 weeks.
Layout redirect exposes content before server enforcement. A developer found that in Next.js, layout-level or client-side redirects can still expose rendered content or underlying data paths before the redirect completes. The HTML is briefly visible in DevTools. A visual redirect is not the same as secure content protection — the server must prevent the response, not just redirect after it.
"I recently integrated Supabase auth into my Next.js app. It took me three days to wrap my head around it." A developer on Reddit described the complexity of getting Supabase auth right in Next.js. The combination of server components, client components, middleware, and cookie management creates multiple points where auth can fail silently.
Detection: How to Check Your Own App
Check 1: Which auth function does your server use?
# Search for getSession vs getUser in server-side code
grep -rn "getSession\|getUser" --include="*.ts" --include="*.tsx" | grep -v node_modules | grep -v "\.test\."
Interpretation: Not every use of getSession() is an exploitable vulnerability — the concern is when it's used for sensitive authorization decisions (access control, role checks, data filtering). If server-side route handlers use getSession() as the basis for who can access what, the session is being trusted without verification. Look for getUser() calls on those sensitive paths instead.
Check 2: Do API endpoints enforce auth independently?
Test your protected API endpoints without a session:
# Call a protected endpoint without auth
curl -s -o /dev/null -w "%{http_code}" https://your-app.com/api/user/data
# Call with a regular user token on an admin endpoint
curl -s -o /dev/null -w "%{http_code}" https://your-app.com/api/admin/users \
-H "Authorization: Bearer REGULAR_USER_TOKEN"
Interpretation:
- 200 without auth = endpoint has no authentication at all
- 200 with wrong role = endpoint has authentication but no authorization
- 401 / 403 = endpoint enforces access control independently
Check 3: Is auth only in middleware?
# Check if middleware handles auth
grep -rn "auth\|session\|getUser\|getSession" middleware.ts middleware.tsx
# Check if API routes have their own auth
grep -rn "auth\|session\|getUser\|getSession" app/api/ --include="*.ts" --include="*.tsx"
Interpretation: If auth logic exists only in middleware and not in individual API route handlers, those routes rely entirely on middleware protection — which has known bypass vulnerabilities.
Related Launch Risks
- Your Admin Panel Has No Lock. — Admin access without server enforcement is the highest-impact version of this pattern.
- Your Database Is Public. You Just Don't Know It Yet. — Auth that looks correct paired with missing RLS creates a double failure: neither the application layer nor the database layer enforces access.
- Anyone Can Upgrade to Pro for Free. — Client-side plan checks without server enforcement are an auth-looks-safe problem applied to billing.
- One Exposed Key Gives Strangers Full Access to Your Database. — When auth is weak AND keys are exposed, the attack surface multiplies.
FAQ
What's the actual difference between getSession() and getUser()?
getSession() reads the session from the browser cookie without verifying it against Supabase's auth server. It's fast but untrustworthy — the cookie is client-controlled and can be modified. getUser() sends the token to Supabase's auth server for verification. It confirms the token is valid, unexpired, and belongs to a real user. For any server-side access decision, getUser() is the correct choice.
We use Next.js middleware for auth. Is that secure?
Middleware adds a useful defense layer, but it should not be the only one. CVE-2025-29927 showed that Next.js middleware can be bypassed entirely. More fundamentally, middleware alone is not a sufficient security boundary — route handlers and API endpoints must verify auth independently, because a direct API call doesn't go through the middleware path.
Our app redirects non-logged-in users to the login page. Isn't that protection?
It's UI protection, not server protection. If the redirect happens in a layout component or client-side code, the page content may still render briefly (visible in DevTools). And the API endpoints behind that page are not affected by the redirect at all — they serve data to anyone who calls them directly.
Does signOut() immediately revoke access?
Not completely. signOut() deletes the session from Supabase's database, but already-issued JWTs remain valid until they expire. With Supabase's default settings, this window can be up to 60 minutes — though the exact duration depends on your JWT expiry configuration. This is an inherent characteristic of JWT-based auth, not a Supabase-specific issue.
AI generated my auth. How likely is it to have this problem?
Very likely. AI tools trained on older Supabase documentation generate getSession() by default. The Supabase SDK itself uses getSession() in some internal calls, which triggers the console warning even when the developer didn't explicitly use it — confusing thousands of developers. Carnegie Mellon research found that only 10.5% of AI-generated solutions were fully secure, despite 61% being functionally correct.