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.
- ·7 min read
"You can only request this after 56 seconds"
Supabase's per-address email cooldown is stored on the user row, so it can only fire for addresses that have an account. That makes the rate limit an oracle.
SupabaseAuthRate LimitsEnumerationSecurity - ·7 min read
Supabase reset password email not sending
resetPasswordForEmail() returns success and sends nothing when the address has no account. That's the anti-enumeration design — here's how to triage it.
SupabaseAuthPasswordsEnumerationSecurity - ·8 min read
Supabase signUp returns a user for any password
Sign up with an existing unconfirmed email and any password at all, and Supabase returns that account's real user object. Here's the branch that does it.
SupabaseAuthEnumerationNext.jsSecurity - ·8 min read
"Signups not allowed for otp": the real cause
Supabase raises "Signups not allowed for otp" only when shouldCreateUser is false and the address has no account — making the error an existence oracle.
SupabaseAuthMagic LinkEnumerationSecurity - ·7 min read
supabase.auth.admin.getUserByEmail is not a function
There is no getUserByEmail in the Supabase admin API. Here's the undocumented filter param that does work, and the four workarounds ranked by blast radius.
SupabaseAuthAdmin APIEnumerationSecurity - ·8 min read
Supabase email_address_invalid on password reset
resetPasswordForEmail can return email_address_invalid for a real, confirmed user while returning plain success for an address that doesn't exist. Here's why.
SupabaseAuthPasswordsEnumerationSecurity - ·8 min read
Supabase RLS with no policies: is it safe?
Enabling RLS with zero policies and routing every read through SECURITY DEFINER RPCs works — but it removes your last backstop. Here's what you take on.
SupabaseRLSPostgresSECURITY DEFINERSecurity - ·7 min read
The Supabase RLS policy that fails open
using (auth.uid() is null or user_id = auth.uid()) returns every row to anonymous callers. Supabase's own linter does not flag it. Here's how to find yours.
SupabaseRLSPostgresSecurity - ·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 - ·9 min read
Supabase "email rate limit exceeded": cause, fix, all limits
Supabase's built-in email provider sends only 2 auth emails per hour per project. The fix is custom SMTP. Plus the other ten Auth limits behind a 429.
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