Securing Next.js + Supabase, in depth
The specific mistakes that get apps on this stack hacked — and the exact fix for each. No generic checklists.
- ·6 min read
Fix: Database error granting user (Supabase)
This login 500 is usually a trigger that fires when sign-in updates auth.users, then throws. Fix the trigger function — don't loosen RLS to make it pass.
SupabaseAuthPostgresTriggersRLS - ·6 min read
Fix: Invalid API key error in Supabase
Invalid API key means the key doesn't match the project at that URL. Fix the key/URL pairing — don't obey the hint by putting service_role in the browser.
SupabaseAPI KeysNext.jsSecurity - ·7 min read
Supabase "Invalid login credentials": 3 real causes
Supabase returns Invalid login credentials for a wrong password, a missing account, and a passwordless account alike. Here's how to tell which one you hit.
SupabaseAuthEnumerationSecurity - ·6 min read
Supabase select returns empty array: RLS, not a bug
An empty [] with no error while the table editor shows rows is RLS filtering the query. Diagnose the policy and role — don't disable RLS to test it.
SupabaseRLSPostgresDebugging - ·7 min read
Supabase auth rate limit exceeded: the full table
429s from Supabase Auth come from nine documented limits — 2 emails/hour, 30 OTPs/hour, a 60-second per-user window. Here's each one and how to work with it.
SupabaseAuthRate LimitsSecurity - ·8 min read
Supabase signup with an existing email: why no error
Supabase returns a fake user instead of "email already exists" on purpose — it blocks user enumeration. The usual workaround puts the hole right back.
SupabaseAuthEnumerationRLSSecurity - ·7 min read
Fix: Error sending confirmation email (Supabase)
This 500 is your email provider failing, not your code. Read the Auth logs for the real reason — and don't fix it by turning email confirmation off.
SupabaseAuthSMTPEmailSecurity - ·7 min read
Fix: Failed to delete user (Supabase)
Database error deleting user is almost always a foreign key pointing at auth.users without ON DELETE CASCADE. Fix the constraint, not the delete.
SupabaseAuthPostgresGDPRRLS - ·7 min read
Fix: Auth session missing in Next.js Route Handlers
Middleware refreshes your Supabase session fine, but getUser() throws AuthSessionMissingError inside /api. Here's why the Route Handler never sees it.
SupabaseNext.jsAuthRoute HandlersSessions - ·8 min read
Fix: Route couldn't be rendered statically (cookies)
Next.js throws DynamicServerError when something swallows the bailout that should opt your Supabase route into dynamic rendering. Here's the real fix.
SupabaseNext.jsApp RouterBuildSessions - ·8 min read
Fix: Supabase invalid flow state, no valid flow state
Supabase's invalid flow state error means the PKCE row can't be matched to the code you're exchanging — usually a misrouted OAuth redirect URI.
SupabaseAuthOAuthPKCENext.js - ·7 min read
Fix: Email link is invalid or has expired (otp_expired)
Supabase's otp_expired error is usually a corporate mail scanner clicking the link before your user does. Here are the two documented fixes.
SupabaseAuthMagic LinkEmailNext.js - ·7 min read
Fix: permission denied for schema public (Supabase)
This error means USAGE on the public schema was revoked from anon and authenticated — usually by an ORM migration. RLS never even gets a chance to run.
SupabasePostgresGrantsPrismaRLS - ·6 min read
Fix 431 REQUEST_HEADER_FIELDS_TOO_LARGE on Vercel
Supabase stores OAuth provider tokens in the session cookie. Add enough scopes and the request header overflows, so Vercel returns 431. Here's the fix.
SupabaseNext.jsVercelAuthCookies - ·6 min read
Fix: output claims do not conform to expected schema
Supabase's custom access token hook must return the whole event object. Return only your new claims and Auth sees null — here's the correct function.
SupabaseAuthJWTRBACPostgres - ·7 min read
Supabase MFA blocks password reset (AAL2 required)
With MFA on, a recovery link yields an AAL1 session but updateUser({password}) demands AAL2, so the reset 401s. Here's the flow that actually works.
SupabaseAuthMFAPasswordsSecurity - ·7 min read
Fix: auth code and code verifier should be non-empty
Supabase's PKCE code verifier lives in a cookie set by the browser that started the login. If your callback route can't read it, the exchange fails.
SupabaseAuthNext.jsOAuthPKCE - ·7 min read
Fix: Invalid Refresh Token: Refresh Token Not Found
Supabase logs users out roughly every day when your middleware never persists the rotated refresh token. Here's the cookie bug and the exact fix.
SupabaseAuthNext.jsSessionsMiddleware - ·6 min read
Fix 42501: must be owner of table objects
Supabase blocked ALTER TABLE on the storage schema in April 2025. RLS is already on for storage.objects — delete the line and just create your policies.
SupabaseStorageRLSMigrations - ·6 min read
Fix: Failed to parse cookie string base64-eyJ
The base64-eyJ cookie parse error means deprecated @supabase/auth-helpers can't read the new cookie format. Migrating to @supabase/ssr is the only real fix.
SupabaseAuthNext.jsDependencies - ·6 min read
Fix: cookies() should be awaited (Next.js 15)
Next.js 15 made cookies() async, which breaks the Supabase server client. Make createClient async and await the cookie store — here's the exact code.
SupabaseNext.jsSessionsAuth - ·8 min read
Fix: Invalid Refresh Token: Already Used
Supabase's Already Used refresh error is a race, not an expiry. Concurrent App Router layouts refresh the same token — here's the cause and the fix.
SupabaseAuthNext.jsSessions - ·6 min read
Fix supabaseUrl is required on Vercel
supabaseUrl is required on a Vercel build means the env var is missing for that environment — not that your code is wrong. Here's the fix, and the trap.
SupabaseNext.jsVercelSecrets - ·6 min read
Fix Supabase 'AuthApiError: User not allowed'
AuthApiError: User not allowed means you called auth.admin with a non-service-role key. Fix it on the server — moving the key to the browser is a breach.
SupabaseAuthSecretsNext.js - ·7 min read
Fix: Database error saving new user (Supabase)
AuthApiError: Database error saving new user almost always means your trigger on auth.users failed. Here's how to read the real error and fix it safely.
SupabaseAuthRLSPostgres - ·7 min read
Supabase JWT signing keys broke auth.uid()
After migrating to asymmetric JWT signing keys, PostgREST can resolve valid tokens as anon — auth.uid() goes NULL and writes silently no-op. The fix.
SupabaseRLSAuthJWT - ·7 min read
Fix PGRST116: multiple (or no) rows returned
PGRST116 means .single() got 0 or 2+ rows. On Supabase it's usually RLS silently filtering the row out — here's how to tell that apart from a bad id.
SupabaseRLSPostgRESTNext.js - ·8 min read
Fix @supabase/ssr 'Detected stale cookie data'
The @supabase/ssr stale cookie warning means leftover auth-token chunk cookies are shadowing the real session. Here's the cause and the exact fix.
SupabaseNext.jsAuthCookies - ·6 min read
Fix: Supabase anonymous sign-ins are disabled
The 422 'Anonymous sign-ins are disabled' error means the provider is off. Enable it in Auth settings — then fix the RLS policies it just widened.
SupabaseAuthRLSErrors - ·6 min read
app_metadata vs user_metadata in Supabase RLS
user_metadata is editable by the user, so a role stored there is self-service admin. Put authorization claims in app_metadata — here's the difference.
SupabaseRLSAuthRBAC - ·6 min read
Supabase multi-tenant RLS: isolating tenant data
Multi-tenant RLS leaks when the policy's subquery isn't scoped to auth.uid(). Here's the tenant isolation pattern that actually holds, with SQL.
SupabaseRLSMulti-tenantPostgres - ·6 min read
Supabase signed URLs: expiry, leaks, revocation
A Supabase signed URL works for anyone who holds it until it expires — and can't be revoked without contacting support. How to scope expiry properly.
SupabaseStorageSigned URLsSecurity - ·6 min read
getSession vs getUser: which is safe on the server?
getSession() reads the cookie without verifying it, so it can be spoofed on the server. Use getUser() or getClaims() for any authorization decision.
SupabaseNext.jsAuthMiddlewareRLS - ·6 min read
Supabase API keys: sb_publishable vs anon vs service_role
The anon and service_role keys are being replaced. sb_publishable_ is safe to ship in the browser; sb_secret_ replaces service_role and bypasses RLS — server only.
SupabaseSecretsRLSNext.js - ·7 min read
Supabase Realtime not working with RLS?
Realtime filters Postgres Changes through your RLS policies, so a subscriber who can't SELECT a row never sees its event. The three causes and the fixes.
SupabaseRLSRealtimePostgres - ·6 min read
Supabase Security Definer View: an RLS bypass
A Postgres view runs as its owner, so a view over an RLS-protected table can return every row to anon. Fix it with security_invoker = on.
SupabaseRLSPostgresDatabase - ·8 min read
Why auth.uid() = user_id fails in Supabase RLS: uuid vs text
auth.uid() returns uuid but a text user_id can't be compared — Postgres won't cast either way, so the RLS policy errors 'operator does not exist: uuid = text'.
SupabaseRLSPostgresAuthType mismatch - ·9 min read
Why supabase.auth.getUser() returns null on the server in Next.js
getUser() returns null on the server because your Supabase client was never wired to the request cookies — no user JWT reaches Auth. Here's the two-file fix.
SupabaseNext.jsAuthSSRMiddleware - ·7 min read
How to fix 'infinite recursion detected in policy' in Supabase RLS
The infinite recursion detected in policy error means an RLS policy queries its own table. Fix it with a SECURITY DEFINER helper function.
SupabaseRLSPostgresSecurity - ·8 min read
Fix: new row violates row-level security policy on Supabase Storage upload
Uploading to Supabase Storage throws new row violates row-level security policy for table objects? Add an INSERT policy on storage.objects. Here's the fix.
SupabaseStorageRLSPostgresAuth - ·9 min read
AWS Cognito + Supabase RLS: setup and gotchas
AWS Cognito works with Supabase RLS via native third-party auth — its sub is UUID-shaped, so auth.uid() works in practice, but auth.jwt()->>'sub' is the portable pattern.
SupabaseRLSCognitoAuthPostgres - ·7 min read
Fix Supabase 'invalid claim: missing sub claim' (401)
Supabase's 'invalid claim: missing sub claim' is a GoTrue Auth error, not PostgREST — you sent an API key where a user access token belonged.
SupabaseAuthJWTGoTrueRLS - ·8 min read
Supabase: tables not exposed to the Data API after the 2026 grant change
In 2026 Supabase stopped auto-exposing tables to the Data and GraphQL API. Why your REST call now returns 404, and the safe grant fix.
SupabaseData APIRLSGrantsPostgREST - ·8 min read
WorkOS and Supabase RLS: fixing the auth.uid() trap for prefixed user ids
WorkOS is natively supported by Supabase, but its user ids are strings, not UUIDs. Here is the correct text-column RLS pattern for WorkOS + Supabase.
supabaseworkosrlsauthkitpostgres - ·5 min read
How to test your Supabase RLS policies
The Supabase SQL editor bypasses RLS, so testing there proves nothing. Here's how to actually test your policies with pgTAP and the client SDK.
SupabaseRLSTestingNext.js - ·6 min read
Is it safe to expose the Supabase anon key?
Yes — the Supabase anon key is public by design, but only if RLS is enabled on every table. Here's what makes it safe, and the one thing that makes it a leak.
SupabaseRLSSecretsNext.js - ·6 min read
Fix: Supabase Leaked Password Protection Disabled
Supabase's Leaked Password Protection Disabled warning means users can sign up with known-breached passwords. Enable it in Auth settings — here's how and why.
SupabaseAuthPasswordsSecurity - ·6 min read
Auth0 + Supabase RLS: the setup and the gotchas
Auth0 works with Supabase RLS via third-party auth — but you must send the ID token (not the access token), add a role claim, and use auth.jwt()->>'sub'.
SupabaseRLSAuth0AuthPostgres - ·6 min read
better-auth + Supabase RLS: the secure way
better-auth isn't a Supabase third-party auth provider, so don't share your JWT secret to fake it. Here's the secure way to enforce RLS under better-auth.
SupabaseRLSbetter-authAuthPostgres - ·9 min read
Clerk + Supabase RLS: why auth.uid() is null
With Clerk auth, auth.uid() fails because Clerk IDs aren't UUIDs — use Supabase's native integration and compare auth.jwt()->>'sub' to a text column.
SupabaseRLSClerkAuthPostgres - ·6 min read
Firebase Auth + Supabase RLS: setup and gotchas
Firebase Auth works with Supabase RLS via third-party auth — add a role:authenticated custom claim, force-refresh the first token, and match auth.jwt()->>'sub'.
SupabaseRLSFirebaseAuthPostgres - ·7 min read
NextAuth + Supabase RLS Without a Shared Secret
NextAuth isn't a Supabase-trusted auth provider, so signing your own JWT is risky. Here is the secure restricted-role pattern that keeps RLS enforced.
NextAuthSupabaseRLSPostgresAuthentication - ·7 min read
Next.js Middleware Auth Bypass: CVE-2025-29927
A spoofable x-middleware-subrequest header let requests skip Next.js middleware, bypassing auth. Upgrade to the patched version; don't trust middleware alone.
Next.jsMiddlewareSecurityCVE - ·10 min read
Does Prisma respect Supabase RLS? No — here's why
Prisma and Drizzle connect as the postgres role and bypass Supabase RLS entirely, so your policies never protect ORM queries. Here's how to fix it.
SupabaseRLSPostgresPrismaDrizzle - ·8 min read
Fix: Stripe Webhook Signature Verification Failed
Stripe webhook signature verification failed in Next.js? The top cause is a parsed body — pass the raw request text to constructEvent. Full fix here.
Next.jsStripeWebhooksSecurity - ·13 min read
Why auth.uid() returns NULL in a Supabase RLS policy
auth.uid() returns NULL when no user JWT reaches Postgres — you tested in the SQL editor, or your server client never forwarded the session.
SupabaseRLSPostgresAuthNext.js - ·6 min read
Supabase CORS error in Next.js: the real fix
A Supabase CORS error in Next.js is usually a bad env var or missing grants, not CORS. Here is how to diagnose it and fix Edge Function CORS properly.
SupabaseNextjsCORSSecurityEdge-Functions - ·8 min read
Fix Supabase JWT Expired (PGRST301) in Next.js
A Supabase PGRST301 'JWT expired' 401 means the access token aged out with nothing refreshing the cookie, and the @supabase/ssr middleware fixes it.
SupabaseNextjsAuthJWTRLS - ·10 min read
Fix: new row violates row-level security policy
Supabase error 42501 on insert means RLS is on but the row failed a WITH CHECK policy. Add or fix an INSERT policy matching auth.uid().
SupabaseRLSPostgresAuthNext.js - ·7 min read
Supabase: permission denied for table (42501)
The 42501 permission denied for table error is a missing Postgres GRANT, not an RLS failure. Here is the exact SQL fix.
SupabasePostgresRLSPermissionsSecurity - ·10 min read
Why Supabase RLS queries are slow (and how to fix)
Supabase RLS queries are slow because auth.uid() re-runs per row and policy columns lack indexes. Here are the verified fixes with SQL.
SupabaseRLSPostgresPerformance - ·5 min read
Supabase security breaches: what actually happened
Supabase itself hasn't been breached — but apps built on it leak data through disabled RLS and exposed keys. The documented incidents and how to check yours.
SupabaseRLSSecurityData Breach - ·15 min read
Supabase Function Search Path Mutable, fixed
The Supabase database linter flags function_search_path_mutable when a SECURITY DEFINER function has no pinned search_path. The real risk, and the exact fix.
SupabasePostgresSecurityRLS - ·6 min read
Is Supabase Secure? What's Safe and What's on You
Yes — Supabase is SOC 2 Type II and safe for production. The risks that actually leak data are in your config: RLS, the service_role key, and exposed secrets.
SupabaseRLSSecuritySecrets - ·7 min read
Next.js App-Layer Security: Beyond RLS and Secrets
Once RLS and secrets are locked down, the Next.js app layer is where bugs hide: XSS, eval, CORS, input validation, dependencies. The footguns and the fixes.
Next.jsSecurityXSSCORS - ·6 min read
Snyk vs GitGuardian vs GuardLayer for Indie Devs
A head-to-head of Snyk, GitGuardian, and GuardLayer for solo devs: what each actually catches, verified 2026 pricing, and which fits a Next.js + Supabase stack.
SecurityToolsNext.jsSupabase - ·6 min read
Why a Static Scanner Can't Stop Prompt Injection
A static scanner has no runtime or taint analysis, so it can't detect prompt injection. Here are the 5 concrete things it does catch in AI-built apps instead.
AISecurityPrompt InjectionSupabase - ·4 min read
Catch API Keys Before They Hit GitHub
A leaked API key is scraped within minutes of hitting GitHub. Here's how to catch secrets with a local pre-commit hook plus a scan on every push.
SecretsGitNext.jsCI - ·5 min read
Keeping Secrets Out of a Next.js App
The four ways secrets leak from a Next.js app — NEXT_PUBLIC_, client imports, hardcoding, and git history — and the exact pattern that keeps each one server-side.
Next.jsSecretsSupabaseSecurity - ·4 min read
Your MCP Config Is Leaking Secrets
MCP config files like .mcp.json get committed to git — and a database password or API key pasted inline leaks to everyone. Here's the ${env} fix.
MCPSecretsAISupabase - ·28 min read
Supabase Row Level Security: the Complete Guide
The complete practical guide to Supabase RLS: grants vs policies, USING vs WITH CHECK, the patterns that hold, what bypasses RLS, and how to verify it.
SupabaseRLSPostgresSecurity - ·4 min read
A Security Checklist for Vibe-Coded SaaS Apps
AI coding tools ship working code with predictable security holes. A concrete checklist to harden a vibe-coded Next.js + Supabase SaaS before real users hit it.
AISupabaseNext.jsSecurityRLS - ·4 min read
Do you need a paid security scanner as a solo dev?
When a free security scanner is genuinely enough for a solo dev — and the specific signals that mean it's time to pay. An honest decision guide, not a sales pitch.
SecurityToolsSolo DevFree Tier - ·4 min read
Free security scanning for a Next.js + Supabase repo (no card)
What you actually get on GuardLayer's free tier: continuous scanning for one Next.js + Supabase repo — every push, PR comments, a merge gate, every check, no credit card.
SecurityNext.jsSupabaseFree Tier - ·9 min read
AI Coding Agents Keep Leaving RLS Off — Catch It Before You Push
Cursor, Lovable, v0 and Claude routinely generate Supabase tables with no RLS — a public-data leak via the anon key. Why it happens and how to catch it.
SupabaseRLSAICursorLovable - ·9 min read
Best Security Scanner for Solo Devs & Indie Hackers (2026)
An honest buyer's guide to security scanners for solo devs, with verified June 2026 pricing for Snyk, GitGuardian, Socket, Semgrep, Sonar, and GuardLayer.
SecurityToolsNext.jsSupabase - ·8 min read
The Lovable RLS Vulnerability (CVE-2025-48757), Explained
CVE-2025-48757: AI-built Lovable apps shipped with Supabase RLS off, leaking data to anyone with the anon key. The root cause, the fix, and a self-check.
LovableSupabaseRLSCVEAI - ·8 min read
Is Your Next.js App Leaking Secrets to the Browser?
The three ways a Next.js app leaks secrets client-side — NEXT_PUBLIC_ misuse, server imports, and serialized props — and how to detect and fix each one.
Next.jsSecretsSecurityApp Router - ·8 min read
The Next.js + Supabase Security Checklist
A pre-launch security checklist for Next.js + Supabase apps covering RLS, secrets, the app layer, and dependencies — each item with the why and the fix.
SupabaseNext.jsRLSSecurityChecklist - ·9 min read
The Supabase MCP Lethal Trifecta: How an Agent Reads Your DB
Hand an AI agent the Supabase service_role key over MCP and you build the lethal trifecta. Here's the real risk and the fix that actually holds.
SupabaseMCPAIRLSPrompt Injection - ·8 min read
Vibe-Coding Security: What Goes Wrong in AI-Built Apps
AI tools like Lovable, Bolt and v0 ship Next.js + Supabase apps fast — and with the same handful of holes. Here are the recurring failures and fixes.
Vibe CodingSupabaseRLSNext.jsAI - ·5 min read
Is dangerouslySetInnerHTML safe? When React's escape hatch turns into XSS
dangerouslySetInnerHTML bypasses React's auto-escaping. Here's exactly when it becomes XSS, why the name is a warning, and the precise fix.
ReactXSSNext.jsSecurity - ·6 min read
eval() in JavaScript: the one line that turns user input into remote code execution
eval() runs whatever string you hand it. The moment any part is user-controlled, it's remote code execution. Here's why, and the exact fix.
JavaScriptevalRCENode.js - ·7 min read
Hardcoded API keys in a Next.js app: why deleting the line isn't enough
A hardcoded API key stays in git history after you delete the line. Here's why, and how to actually rotate it and purge it from your Next.js repo.
SecretsGitNext.jsStripe - ·6 min read
NEXT_PUBLIC_ leaked my API key — how it happens and how to catch it
NEXT_PUBLIC_ inlines any value into your client bundle. Here's how it ships your API key to every visitor — and why anon keys are safe but secrets aren't.
Next.jsSecretsAPI KeysEnv Vars - ·6 min read
How to validate the request body in a Next.js API route (and why request.json() is a hole)
request.json() returns any, so attackers can send any shape. Here's the mass-assignment risk and the exact zod safeParse fix for Next.js routes.
Next.jsValidationZodSecurity - ·6 min read
Access-Control-Allow-Origin: * — the CORS wildcard that quietly leaks your Next.js API
Access-Control-Allow-Origin: * in a Next.js route hands your API responses to any website. Why it leaks data, and the exact allowlist fix.
Next.jsCORSAPISecurity - ·6 min read
Your Next.js middleware runs on every request — add a matcher
Next.js middleware with no config.matcher runs on every request — static assets, images, prefetches included. Why that's a bug, and the exact fix.
Next.jsMiddlewareAuthPerformance - ·7 min read
Are Next.js Server Actions secure? The auth check everyone forgets
Server Actions are public POST endpoints anyone can call. Here's why a 'use server' mutation with no auth check is exploitable — and the fix to add.
Next.jsServer ActionsAuthApp Router - ·6 min read
Supabase Edge Functions are public by default — verify the JWT
Supabase Edge Functions are publicly invocable the second you deploy them. Here is why that is dangerous and how to verify the caller's JWT.
SupabaseEdge FunctionsAuthJWT - ·7 min read
Stop building SQL strings: injection in Next.js + Supabase apps
Interpolating a user value into a SQL string is how SQL injection reaches Supabase apps — even behind RLS. Here's the risk and the exact fix.
SupabaseSQL InjectionNext.jsRPC - ·7 min read
Public Supabase storage buckets: the silent data leak
A public Supabase storage bucket makes every object readable by anyone with the URL — no auth, no RLS. Here's why public: true leaks user data, and the fix.
SupabaseStorageSigned URLsSecurity - ·14 min read
Disabling RLS in Supabase: what it exposes and the fix
Disabling Row Level Security on a Supabase table makes it fully public via the anon key. What it exposes, how to re-enable RLS, and the exact fix.
SupabaseRLSPostgresMigrations - ·6 min read
Your Supabase RLS policy isn't scoped to the user: the missing auth.uid()
An RLS policy with a real WHERE-style condition can still leak every user's rows. The cause: the predicate never references auth.uid().
SupabaseRLSPostgresSecurity - ·7 min read
Your Supabase RLS is enabled and still wide open: the USING (true) trap
RLS being enabled doesn't mean it protects anything. A policy with USING (true) returns every row to every caller.
SupabaseRLSPostgresSecurity - ·6 min read
Hardcoding your Supabase service_role JWT is a permanent breach, even after you delete it
Pasting the service_role JWT (eyJ...) straight into source bypasses every RLS policy and lives in git history forever. Here's why, and the exact fix.
SupabaseSecretsJWTGit - ·8 min read
Every Supabase table needs RLS — the one you forgot is public
New Supabase tables ship with RLS off. The one table you forgot to enable it on is readable by anyone holding your anon key. How to catch it.
SupabaseRLSPostgresMigrations - ·6 min read
Vulnerable npm dependencies: catch them before they ship, not after
Most breaches start in node_modules, not your code. Here's how known-vulnerable npm packages slip past review — and how to stop them at the PR.
npmDependenciesNext.jsSupply Chain - ·5 min read
Your Supabase service role key is one NEXT_PUBLIC_ away from a full database breach
The Supabase service_role key bypasses every RLS policy. Here's exactly how it leaks into the browser, why it's catastrophic, and the precise fix.
SupabaseRLSSecretsNext.js