Back to Resources
Supabase

Supabase Production Security Guide: Harden Your Backend Before Launch

BI
Bilal Nazam
April 9, 20258 min read

Security Before You Launch

A misconfigured Supabase backend is a serious security risk. Exposed API keys, missing RLS policies, or weak auth configuration can lead to data breaches. This guide covers every security layer you should configure before going to production.

1. API Key Management

Supabase has two key types with very different purposes:

  • Anon key: Safe to expose on the client. Respects RLS policies. Only allows what your policies permit.
  • Service role key: Bypasses ALL security. Never expose to clients. Only use server-side.
// ✅ Safe: anon key in client code
const supabase = createClient(url, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY)

// ✅ Safe: service role key in server-side only
const supabaseAdmin = createClient(url, process.env.SUPABASE_SERVICE_ROLE_KEY)

// ❌ NEVER: service role key in client-side code or exposed in browser

2. Enable and Test RLS on Every Table

-- Verify all public tables have RLS enabled
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;

-- Any table with rowsecurity = false is a security risk

Test RLS as the anon role from the Supabase SQL editor:

-- Test as anon user (no auth)
SET LOCAL role TO anon;
SELECT * FROM profiles; -- Should return 0 rows if RLS is correct

3. Auth Security Hardening

-- In Supabase Dashboard → Auth → Settings:
-- ✅ Enable email confirmation
-- ✅ Set minimum password length (12+)
-- ✅ Enable leaked password protection (HaveIBeenPwned)
-- ✅ Set session timeout
-- ✅ Restrict sign-up by email domain if needed

For OAuth providers, only enable what you actually use. Each enabled provider is an additional attack surface.

4. Validate JWT Claims in RLS

Use JWT metadata to enforce additional access rules:

-- Only allow verified email users
CREATE POLICY "verified_users_only" ON sensitive_data FOR SELECT
  USING (
    auth.jwt() ->> 'email_confirmed_at' IS NOT NULL
  );

-- Only allow specific email domains
CREATE POLICY "company_email_only" ON internal_data FOR SELECT
  USING (
    auth.jwt() ->> 'email' LIKE '%@yourcompany.com'
  );

5. Protect Against Mass Data Export

-- Add rate limiting via custom claims or limit rows returned
CREATE POLICY "limit_export" ON user_data FOR SELECT
  USING (
    auth.uid() = user_id -- Users can only see their own data
    -- Combined with application-level pagination, prevents mass export
  );

-- For APIs, always use pagination
const { data } = await supabase
  .from('sensitive_table')
  .select('*')
  .limit(100) // Never allow unlimited selects

6. Input Validation for Edge Functions

// Always validate inputs in Edge Functions
serve(async (req) => {
  const body = await req.json()

  // Validate required fields
  if (!body.email || typeof body.email !== 'string') {
    return new Response('Invalid email', { status: 400 })
  }

  // Sanitize to prevent injection
  const email = body.email.trim().toLowerCase()
  if (!email.match(/^[^s@]+@[^s@]+.[^s@]+$/)) {
    return new Response('Invalid email format', { status: 400 })
  }

  // Proceed safely...
})

7. Secure Storage Buckets

-- Block direct public access to private buckets
-- In Supabase Dashboard → Storage → Bucket → Edit:
-- ✅ Set bucket to private
-- ✅ Add RLS policies for the storage.objects table

-- Restrict file types
CREATE POLICY "images_only" ON storage.objects FOR INSERT
  WITH CHECK (
    bucket_id = 'avatars'
    AND (storage.extension(name) IN ('jpg', 'jpeg', 'png', 'webp'))
    AND (metadata->>'size')::integer < 5242880 -- 5MB max
  );

8. Set Up Monitoring and Alerts

  • Enable Supabase audit logs (Dashboard → Database → Logs)
  • Set up alerts for unusual query volumes
  • Monitor auth failure rates (high failures = potential attack)
  • Use pg_stat_statements to detect unusual query patterns
-- Find unusual activity: user accessing many rows suddenly
SELECT
  auth.uid() as user_id,
  query,
  calls,
  rows
FROM pg_stat_statements
WHERE calls > 1000 -- Flag queries called more than 1000 times
ORDER BY calls DESC;

9. CORS Configuration

In Supabase Dashboard → API Settings, set your API URL whitelist to only your actual domains. Don't leave it as wildcard (*) in production.

Pre-Launch Security Checklist

  • ☐ Service role key is NOT in client code or git repo
  • ☐ RLS enabled on all public tables
  • ☐ All RLS policies tested as anon and authenticated
  • ☐ Auth email confirmation enabled
  • ☐ Storage buckets have appropriate policies
  • ☐ All API inputs validated in Edge Functions
  • ☐ CORS configured for your domains only
  • ☐ Monitoring and alerting configured

Migrating from Lovable Cloud and want a security-first setup? Our migration service includes a full security configuration review.

Categorized In

supabasesecurityproductionrlsauth

Frequently Asked Questions

Is Supabase secure for handling sensitive user data?

Yes, when properly configured. The key is enabling RLS on all tables, keeping the service role key server-side only, and validating all inputs. Supabase infrastructure itself is SOC2 Type II certified.

Can hackers access my Supabase database if they get my anon key?

Not if RLS is properly configured. The anon key only allows what your RLS policies permit. A well-configured RLS setup means the anon key is safe to expose publicly — it's designed to be.

How do I rotate compromised Supabase API keys?

Go to Supabase Dashboard → Settings → API → Regenerate keys. This invalidates old keys immediately. Update your environment variables across all deployments. Your database data is not affected by key rotation.

Share This Intelligence

Start Your Migration Strategy

Don't let vendor lock-in stifle your growth. Get a professional roadmap to Supabase excellence today.

Free Architectural Audit