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 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
Supabase "Database error saving new user": the trigger fix
Usually a trigger on auth.users threw. Read the real error in Postgres Logs, then make the function SECURITY DEFINER with pinned search_path. Keep RLS on.
SupabaseAuthRLSPostgres - ·7 min read
Supabase JWT signing keys migration: fix auth.uid() NULL
After moving to new JWT signing keys, the Data API can read valid tokens as anon, so auth.uid() is NULL. Re-align the key and verify with getClaims().
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
Supabase anonymous sign-ins: enable them, fix "disabled"
Enable the Anonymous provider under Authentication → Sign In / Providers or in config.toml. Getting "Anonymous sign-ins are disabled" on a normal signUp()? Your email is arriving empty.
SupabaseAuthRLSErrors - ·6 min read
Supabase user_metadata vs app_metadata: who can update each
Any signed-in user can change their own user_metadata with updateUser(). app_metadata is service_role-only. Where roles go, and how to set and read each.
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 URL expiration: what expiresIn to set
Supabase signed URLs last expiresIn seconds and have no revoke API. How to create one, what expiry to set, and why rotating keys won't kill it.
SupabaseStorageSigned URLsSecurity - ·7 min read
Supabase getSession vs getUser vs getClaims: which to use
Use getClaims() or getUser() on the server, not getSession(): it trusts the cookie unverified, so Supabase warns it could be insecure. Code fix included.
SupabaseNext.jsAuthMiddlewareRLS - ·9 min read
Supabase API keys: publishable, secret, anon and service_role
Which Supabase API key is safe in the browser and which bypasses RLS: publishable vs anon, secret vs service_role, and what happens when the legacy keys go away.
SupabaseSecretsRLSNext.js - ·7 min read
Supabase Realtime not working with RLS? 3 causes and fixes
No Realtime events after enabling RLS? Add the table to the supabase_realtime publication, give subscribers a SELECT policy, and subscribe after sign-in.
SupabaseRLSRealtimePostgres - ·6 min read
Supabase "Security Definer View" error: the 1-line SQL fix
Lint 0010 means the view runs as its owner, so RLS is skipped and anon can read every row. Fix: alter view ... set (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