Complete Guide Updated January 2026

Supabase Security Best Practices

Everything you need to know to secure your Supabase application. From Row Level Security to API key management, this guide covers all the security fundamentals and advanced patterns.

1. Row Level Security (RLS)

Row Level Security is your primary defense mechanism in Supabase. It ensures that even if someone gets your anon key (which is designed to be public), they can only access data they're authorized to see.

Enable RLS on Every Table

The first rule is simple: enable RLS on every table in your public schema. Tables without RLS are accessible to anyone with your project URL and anon key.

-- Enable RLS on a table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Verify RLS is enabled
SELECT tablename, rowsecurity 
FROM pg_tables 
WHERE schemaname = 'public';
Critical Warning

A table with RLS enabled but no policies blocks ALL access (except for service_role). Always add at least one policy after enabling RLS.

Common RLS Patterns

Here are the most common patterns you'll use:

User owns row:

CREATE POLICY "Users can access own data"
  ON user_profiles FOR ALL
  USING (user_id = auth.uid());

Public read, private write:

-- Anyone can read
CREATE POLICY "Public read" ON posts FOR SELECT USING (true);

-- Only owner can modify
CREATE POLICY "Owner write" ON posts FOR INSERT 
  WITH CHECK (user_id = auth.uid());

The WITH CHECK Clause

For INSERT and UPDATE operations, use WITH CHECK to validate the data being written. This prevents users from inserting rows they shouldn't own:

CREATE POLICY "Insert own posts" ON posts FOR INSERT
  WITH CHECK (user_id = auth.uid());

CREATE POLICY "Update own posts" ON posts FOR UPDATE
  USING (user_id = auth.uid())
  WITH CHECK (user_id = auth.uid());

2. API Key Management

Supabase gives you two main API keys: anon and service_role. Understanding the difference is crucial for security.

Anon Key (Public)

Service Role Key (Secret)

Never Do This

Never put your service_role key in client-side JavaScript, React components, or anywhere it could be extracted from your app bundle.

Key Storage Best Practices

# .env.local (client-side - safe)
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

# .env (server-side only - keep secret)
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

3. Authentication Security

Enable Email Confirmation

Prevent fake accounts by requiring email confirmation before users can access your app:

  1. Go to Authentication → Settings in your Supabase dashboard
  2. Enable "Confirm email"
  3. Configure your email templates

Password Requirements

Set minimum password strength requirements to prevent weak passwords. Supabase supports configuring minimum length and complexity rules.

Configure Redirect URLs

Whitelist only your domains in the redirect URL settings. This prevents open redirect vulnerabilities where attackers could redirect users to malicious sites.

// Allowed redirect URLs (configure in dashboard)
https://yourapp.com/*
https://www.yourapp.com/*
http://localhost:3000/* // For development only

Rate Limiting

Supabase has built-in rate limiting for auth endpoints. Review the default limits and adjust if needed:

4. Storage Security

Supabase Storage has its own RLS policies separate from database tables.

Enable RLS on Buckets

-- Example: Users can only access their own files
CREATE POLICY "User folder access"
  ON storage.objects FOR ALL
  USING (bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1]);

Validate File Types

Restrict uploads to specific file types to prevent malicious file uploads:

CREATE POLICY "Images only"
  ON storage.objects FOR INSERT
  WITH CHECK (
    bucket_id = 'images' AND
    (storage.extension(name) = 'jpg' OR
     storage.extension(name) = 'png' OR
     storage.extension(name) = 'webp')
  );

5. Edge Functions & Database Functions

Edge Functions Security

Always validate the JWT token in your Edge Functions before performing sensitive operations:

import { createClient } from '@supabase/supabase-js'

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

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_ANON_KEY')!,
    { global: { headers: { Authorization: authHeader } } }
  )

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

  // User is authenticated, proceed...
})

Database Functions

Be careful with SECURITY DEFINER functions - they run with elevated privileges:

-- SECURITY INVOKER (default, recommended)
-- Runs with caller's permissions
CREATE FUNCTION get_user_data()
RETURNS TABLE (...)
SECURITY INVOKER
AS $$
  SELECT * FROM users WHERE id = auth.uid();
$$;

-- SECURITY DEFINER (use carefully)
-- Runs with function owner's permissions
CREATE FUNCTION admin_get_all_users()
RETURNS TABLE (...)
SECURITY DEFINER
AS $$
  -- Add your own permission check!
  IF NOT is_admin(auth.uid()) THEN
    RAISE EXCEPTION 'Not authorized';
  END IF;
  SELECT * FROM users;
$$;

6. Common Security Mistakes

Mistake 1: Forgetting RLS on New Tables

Every time you create a table, immediately enable RLS. Make it a habit.

Mistake 2: Overly Permissive Policies

-- BAD: Anyone can do anything
CREATE POLICY "open_access" ON users FOR ALL USING (true);

-- GOOD: Users can only access their own data
CREATE POLICY "user_access" ON users FOR ALL USING (id = auth.uid());

Mistake 3: Exposing Service Role Key

Never use environment variables prefixed with NEXT_PUBLIC_ or VITE_ for your service role key.

Mistake 4: Not Testing Policies

Test your RLS policies by impersonating different users in the SQL editor. Use the role switcher to test as anon, authenticated, or specific users.

Mistake 5: Trusting Client Data

Always validate data on the server. RLS policies should not rely on client-provided user IDs - always use auth.uid().

7. Security Checklist

Quick Security Checklist

Use our interactive security checklist to track your progress with saved state.

Automate Your Security Audits

SupaExplorer can scan your Supabase project automatically and generate AI-powered fix recommendations.

Run Free Audit