Security Guide

Supabase Security Guide

Everything you need to secure your Supabase application. RLS policies, auth configuration, API key management, and common pitfalls to avoid.

Quick Security Check

Run this in your Supabase SQL editor to check if RLS is enabled on your tables:

SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';

-- All tables should show rowsecurity = true

Understanding Supabase Security Model

Supabase security is built on three pillars:

Row Level Security

Database-level policies that control who can read/write each row

Authentication

User identity management with JWT tokens

API Keys

Anon key (public) vs Service key (private)

Critical Misconception

The Supabase anon key is not a secret. It's designed to be exposed in your frontend. Security comes from RLS policies, not from hiding this key.

Row Level Security (RLS) Deep Dive

Enable RLS on All Tables

RLS must be explicitly enabled on each table. Without it, anyone with the anon key can read all data.

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

-- Verify RLS is enabled
SELECT tablename, rowsecurity
FROM pg_tables
WHERE tablename = 'posts';

Writing Effective RLS Policies

Bad: Performance issues
-- Re-evaluates for every row
CREATE POLICY "Users see own"
ON posts FOR SELECT
USING (auth.uid() = user_id);
Good: Cached evaluation
-- Evaluates once per query
CREATE POLICY "Users see own"
ON posts FOR SELECT
USING ((select auth.uid()) = user_id);

Wrapping auth.uid() in a SELECT caches the result, significantly improving query performance.

Common RLS Patterns

Users own their data

CREATE POLICY "Users CRUD own data" ON "todos"
FOR ALL TO authenticated
USING ((select auth.uid()) = user_id)
WITH CHECK ((select auth.uid()) = user_id);

Public read, owner write

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

CREATE POLICY "Owner can update" ON "posts"
FOR UPDATE TO authenticated
USING ((select auth.uid()) = author_id);

Team-based access

CREATE POLICY "Team members access" ON "projects"
FOR ALL TO authenticated
USING (
  EXISTS (
    SELECT 1 FROM team_members
    WHERE team_members.team_id = projects.team_id
    AND team_members.user_id = (select auth.uid())
  )
);

API Key Security

Key TypeCan be Public?Respects RLS?Use Case
anon keyYesYesFrontend client
service_role keyNO!No (bypasses)Server-side only

Never Expose service_role Key

The service_role key bypasses all RLS policies. If exposed, attackers have full database access.

Where to Use Each Key

  • Frontend (React, Vue, etc.): Use anon key only
  • API Routes (Next.js, etc.): Can use service_role
  • Edge Functions: Can use service_role
  • Webhooks: Can use service_role

Common Supabase Security Mistakes

Disabling RLS for 'easier development'

Impact: Anyone can read/write all data in your database
Fix: Always develop with RLS enabled. Use service_role in seed scripts if needed.

Using service_role key in frontend

Impact: Full database access for anyone who inspects your code
Fix: Only use anon key in frontend. Move privileged operations to API routes.

Forgetting RLS on new tables

Impact: New features ship with exposed data
Fix: Create a migration template that always includes RLS.

SELECT policies without proper joins

Impact: N+1 query performance issues, policy bypasses
Fix: Use EXISTS subqueries for related table checks.

Not restricting storage buckets

Impact: Users can access/overwrite other users' files
Fix: Configure storage policies like table RLS.

Frequently Asked Questions

Is it safe to expose my Supabase URL and anon key?

Yes, these are designed to be public. Your security comes from RLS policies, not from hiding these values. Think of them like a read-only API endpoint that respects user permissions.

How do I test if my RLS policies work?

Create two test users, log in as each, and try to access each other's data. Or use Supabase's SQL editor with 'SECURITY DEFINER' vs 'SECURITY INVOKER' to test policies.

Can I use Supabase without RLS?

Technically yes, but you'd need to put all database calls behind authenticated API routes and never use the client directly. RLS is almost always the better choice.

What about Supabase Edge Functions security?

Edge Functions run server-side and can safely use the service_role key. Validate inputs, use typed responses, and don't expose internal errors to clients.

Verify Your Supabase Security

Scan your Supabase application for misconfigurations, exposed keys, and RLS issues.

Scan Your App Free

Last updated: January 2025