Supabase

Supabase Security Best Practices

Secure your Supabase application with these essential practices. From RLS policies to API key management.

Verify your app follows these best practices automatically.

Supabase provides powerful security features, but they need to be configured correctly. These practices help you leverage Supabase's security capabilities effectively.

Quick Wins

Run: SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND NOT rowsecurity;
Audit which keys are in your client code
Test RLS by querying as different users
Enable email confirmation for auth
Review storage bucket policies

Security Best Practices

#1Enable RLS on Every Table

critical

Row Level Security is your primary defense. Without it, any authenticated user can read all data in a table.

Implementation

ALTER TABLE your_table ENABLE ROW LEVEL SECURITY; then create appropriate policies

Don't do this
-- No RLS enabled, all users can read all rows
SELECT * FROM profiles;
Do this instead
-- RLS policy ensures users only see their own data
CREATE POLICY "Users can view own profile" ON profiles
  FOR SELECT USING (auth.uid() = user_id);

#2Never Expose service_role Key

critical

The service_role key bypasses all RLS. It should only be used in secure server environments, never in client code.

Implementation

Use anon key for client-side, service_role only in server functions with restricted access

#3Use (select auth.uid()) in RLS Policies

high

Wrap auth functions in select for better performance. This prevents re-evaluation for each row.

Implementation

Use (select auth.uid()) = user_id instead of auth.uid() = user_id

Don't do this
USING (auth.uid() = user_id)  -- Re-evaluates for each row
Do this instead
USING ((select auth.uid()) = user_id)  -- Evaluates once per query

#4Validate Data Types in RLS Policies

high

Ensure your RLS policies handle type mismatches correctly to prevent bypasses.

Implementation

Cast UUIDs explicitly and handle null cases in policies

#5Use Supabase Auth for Authentication

high

Supabase Auth integrates with RLS through auth.uid(). Custom auth loses this integration.

Implementation

Use Supabase Auth hooks and the auth schema, not custom user tables for authentication

#6Secure Edge Functions

medium

Edge Functions run with service_role by default. Validate authentication in each function.

Implementation

Always verify the JWT and check user permissions in Edge Functions

Common Mistakes to Avoid

Forgetting RLS on new tables

Why it's dangerous:

New tables have RLS disabled by default, exposing all data

How to fix:

Always enable RLS immediately when creating tables

Using service_role key in frontend

Why it's dangerous:

Gives every user full database access, bypassing all security

How to fix:

Only use anon key in client code, service_role in secure server environments

Overly permissive RLS policies

Why it's dangerous:

Policies like 'true' for SELECT allow anyone to read everything

How to fix:

Always scope policies to the authenticated user's data

Verify Your Supabase App Security

Following best practices is the first step. Verify your app is actually secure with a comprehensive security scan.

Scan Your App Free

Frequently Asked Questions

Is the anon key safe to expose in frontend code?

Yes, the anon key is designed to be public. It can only access data permitted by your RLS policies. The service_role key is the secret one that must never be exposed.

How do I check if my tables have RLS enabled?

In Supabase dashboard, go to Table Editor - tables without RLS show a warning. Or run: SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';

Can I use service_role key in Edge Functions?

Yes, Edge Functions run server-side and can safely use service_role. But always validate the user's JWT first and ensure they're authorized for the operation.

Last updated: January 2026