Skip to main content
Back to blog
May 12, 2026|7 min read|Antoine Duno|Developer Tools

Supabase Security Checklist Before Launch — RLS, Auth & API Keys

Most Supabase apps go live with at least one critical misconfiguration. This checklist covers RLS policies, exposed anon keys, Auth hardening, Storage rules, and what to scan before your first user signs up.

Antoine Duno

838 words

AD

Antoine Duno

Founder of ZeriFlow · 10 years fullstack engineering · About the author

Key Takeaways

  • Most Supabase apps go live with at least one critical misconfiguration. This checklist covers RLS policies, exposed anon keys, Auth hardening, Storage rules, and what to scan before your first user signs up.
  • Includes copy-paste code examples and step-by-step instructions.
  • Free automated scan available to verify your implementation.

Most Supabase apps that go live have at least one critical misconfiguration. Not because developers are careless — because Supabase is incredibly fast to ship with, and security configuration is a separate layer that the platform does not enforce by default. This checklist covers the six areas that cause the most incidents: Row Level Security, anon key exposure, Auth configuration, Storage rules, Edge Functions, and security headers.

If you check all six before your first real user signs up, you will have eliminated the majority of attack surface that vibe-coded Supabase apps leave open.

1. Row Level Security (RLS)

Is your site actually secure?

Run a free check — 60 seconds

Scan free →

Row Level Security is the most important security feature in Supabase and the most frequently misconfigured one.

What goes wrong: During development, developers often enable RLS and then add a permissive policy so they can work without interruptions. The classic mistake:

sql
-- WRONG: This allows anyone to read everything
CREATE POLICY "allow all" ON public.profiles
  FOR SELECT USING (true);

This policy satisfies the "RLS is enabled" requirement while providing zero protection. Anyone who knows your Supabase URL can query every row in the table.

What correct looks like:

sql
-- CORRECT: Users can only see their own profile
CREATE POLICY "users can view own profile" ON public.profiles
  FOR SELECT USING (auth.uid() = user_id);

-- CORRECT: Users can only update their own profile
CREATE POLICY "users can update own profile" ON public.profiles
  FOR UPDATE USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);

Before launch checklist for RLS: - Every table with user data has RLS enabled - No policy uses USING (true) on a table with sensitive data - Insert policies use WITH CHECK to validate ownership - You have tested policies as an authenticated user AND as an anonymous user - You have tested cross-user isolation (user A cannot read user B data)

To audit your policies, run this in the Supabase SQL editor:

sql
SELECT schemaname, tablename, policyname, cmd, qual
FROM pg_policies
WHERE schemaname = 'public'
ORDER BY tablename;

2. Anon Key Exposure

The Supabase anon key is designed to be public — it is what your frontend uses to interact with Supabase from the browser.

What you must never do: Use the service_role key on the client side. The service_role key bypasses all RLS policies.

javascript
// WRONG: Never use service_role in client code
const supabase = createClient(url, process.env.SUPABASE_SERVICE_ROLE_KEY)

// CORRECT: Use anon key on client, service_role only in server-side code
const supabase = createClient(url, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY)

In Settings > API, restrict the anon key to your production domain before launch.

3. Auth Configuration

Email enumeration protection: Enable this in Authentication > Settings. Without it, different responses for "email not found" vs "wrong password" let attackers probe which email addresses have accounts.

Redirect URL whitelist: Set your Site URL and add only your production domains. Remove default wildcard entries.

Signup control: If your app is invitation-only, disable public signup in Authentication > Settings.

Rate limiting: Tighten limits on /auth/v1/token and /auth/v1/signup to stop credential stuffing attacks.

4. Storage Security

Use public buckets only for genuinely public assets. All user-uploaded content should be in private buckets with RLS policies on storage.objects:

sql
CREATE POLICY "users can upload own files" ON storage.objects
  FOR INSERT WITH CHECK (
    bucket_id = 'user-uploads' AND
    auth.uid()::text = (storage.foldername(name))[1]
  );

For sensitive files, generate signed URLs server-side:

javascript
const { data } = await supabase.storage
  .from('private-docs')
  .createSignedUrl(filePath, 3600) // expires in 1 hour

5. Edge Functions

Always validate the JWT in edge functions:

typescript
const authHeader = req.headers.get('Authorization')
if (!authHeader) return new Response('Unauthorized', { status: 401 })

const { data: { user } } = await supabaseClient.auth.getUser()
if (!user) return new Response('Unauthorized', { status: 401 })

Never hardcode the service_role key in function code. Do not use Access-Control-Allow-Origin: * in functions that handle authenticated operations.

6. Security Headers

Supabase does not add security headers to your frontend. Configure them in your Next.js deployment:

javascript
// next.config.js
const securityHeaders = [
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
]

module.exports = {
  async headers() {
    return [{ source: '/(.*)', headers: securityHeaders }]
  }
}

Verify Everything Before Launch

  1. 1Every table with user data has RLS enabled with ownership-based policies
  2. 2No USING (true) policies on sensitive tables
  3. 3service_role key is only used server-side
  4. 4Email enumeration protection is enabled in Auth settings
  5. 5Redirect URL whitelist is set to your production domain only
  6. 6All user-uploaded content is in private buckets with RLS policies
  7. 7Edge functions validate JWT before processing
  8. 8Security headers are configured on your frontend deployment

Scan your deployed app at zeriflow.com/free-scan to catch security header issues, exposed endpoints, and TLS misconfigurations automatically. The scan takes 60 seconds.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading