Skip to main content
友田 陽大

Supabase RLS

Supabase Row Level Security, done right

RLS is where your app's real authorization lives — written in SQL, at the row. This is the hub: the one idea that matters, the correct patterns, and free tools to write, verify, and prove your policies. Start with what you need.

npx @aegiskit/cli scan

Scan your whole repo locally — no install, contacts nothing.

9.2%of 1,000 public Supabase apps ship RLS that authenticates but doesn't authorize (a lower bound).

The one idea

Authenticate ≠ authorize

RLS being enabled is not the same as RLS being correct. A policy that only proves a session exists (auth.role() = 'authenticated', auth.uid() is not null) lets every logged-in user read every row. Authorization binds the row to its owner.

Authenticates, but doesn't authorize — every logged-in user reads all rows
create policy "notes_read" on public.notes
  for select to authenticated
  using (auth.role() = 'authenticated');
Scoped to the owner — each user reads only their own
create policy "notes_read" on public.notes
  for select to authenticated
  using ((select auth.uid()) = user_id);

"Not owner-scoped" isn't automatically "vulnerable" — a shared reference table legitimately allows all authenticated reads. The point is to verify intent, not to cry wolf. No tool proves your authorization is correct; it can only point accurately.

Read the deep-dive: authenticate vs authorize

The patterns

Four shapes cover almost everything

Most tables fit one of these ownership models. Generate the exact SQL for yours in seconds.

  • Personal

    Each row belongs to one user; they read and write only their own.

  • Multi-tenant

    Rows belong to a tenant; users access rows for tenants they're a member of.

  • Shared read + owner write

    Any signed-in user reads; only the owner writes. For reference/shared data.

  • Locked

    No client access at all — reads and writes go only through the server.

Generate this policy

Go deeper

The Supabase RLS guides

Practical, source-backed deep dives — the spokes to this hub.

Get your Supabase RLS right

Generate a correct policy in seconds, or have the whole access-control design reviewed end to end.

npx @aegiskit/cli scan

Free, local, nothing sent.

FAQ

  • What is Row Level Security in Supabase?

    RLS is PostgreSQL's per-row access control. In Supabase it's the real authorization boundary: because tables are exposed over an API, a policy on each table decides which rows a caller may read or write. Enable it, then add policies that bind rows to the caller.

  • Is enabling RLS enough to be secure?

    No. Enabling RLS with no policy denies all access (safe), but a policy that only checks the caller is logged in (auth.role() = 'authenticated') lets every user read every row. Correct policies scope rows to their owner, e.g. using ((select auth.uid()) = user_id).

  • How do I write a correct Supabase RLS policy?

    Bind each row to the caller: for reads use USING ((select auth.uid()) = user_id); for writes add a matching WITH CHECK. The free policy generator produces the exact SQL for personal, multi-tenant, shared-read, or locked tables.

  • How do I check if my RLS is correct?

    Paste a policy into the free in-browser RLS checker to see whether it scopes rows, or run npx @aegiskit/cli scan over your whole repo. Neither sends your code anywhere. For a definitive answer on your data model, a human review or audit is the right tool.

  • Does RLS help with SOC 2 or ISO 27001?

    Row-level access control is technical evidence for logical-access controls (SOC 2 CC6.1, ISO 27001 A.8.3). Aegis maps findings to those controls — a reference mapping for auditors, not a certification.