Skip to content

Supabase · RLS · Auth

Pro-gating a dashboard route in Supabase RLS without losing your mind

The view is the gate. The component check is UX.

🔒 🚪

If you've been living under a rock, every SaaS tutorial gates premium routes with a React conditional that reads profile.is_pro and renders a paywall. Cute. Open the network tab and the underlying Supabase query still runs against the free user's session. The gate is theatre.

Spoiler: the real gate lives in Postgres. The component check is purely cosmetic. Behold.

The Setup

Add a boolean to profiles. Add a role-check helper. Then forbid the helper from cascading into other policies by marking it SECURITY DEFINER. The recursion-stack-blowup that bricks half the RLS deployments on Reddit comes from helpers that themselves trigger RLS — definer functions sidestep the policy evaluation when called from inside one.

The Money Pattern

The dashboard query goes through a SECURITY INVOKER view. The view's where-clause does the gating, which means RLS still applies to the underlying tables but the user-facing surface refuses to return rows unless the caller is admin or pro. The client cannot bypass it because there is nothing to bypass — the rows do not exist for them.

Then the policies on dashboard_metrics stay sane — tenant isolation, nothing exotic — and the view layer carries the pro-gate. One concern per layer.

The Catch

SECURITY INVOKER views need the with (security_invoker = true) clause explicitly — Postgres 15+ default is still definer for backwards compatibility. Miss it and your view runs as the view owner, which is normally postgres, which means RLS is silently bypassed. Plot twist: that is the single most common Supabase footgun shipped to production this year.

Also, never recurse policies. If table A's policy calls a helper that selects from table B which selects from table A, Postgres will spend exactly as long as the planner allows before exploding. Definer functions break the loop.

The Verdict

Pro-gate at the view, isolate tenants at the table, treat the React check as UX only. Three layers, zero overlap, zero bypass. The internet collectively lost its mind over Stripe webhooks, but the actual access control is one boolean column and one view. Do not @ me about JWT custom claims — they have their place, this is not it.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.