FOR SECURE APPS
You're spending hours writing complex middleware, checking permissions in every controller, and praying you didn't miss a single `where` clause. Stop that shit. Use the database.
You have 500 endpoint files, 20 custom auth helpers, and you're still not sure if a malicious user can edit someone else's profile by guessing an ID. This is not how you build secure software.
This is your current workflow:
// In your API route...
const { id } = req.body;
const user = await db.getUser(req.userId);
if (user.role !== 'admin' && user.id !== id) {
return res.status(403).send('F*ck off');
}
// 10 lines of security code for 2 lines of logic.
await db.updateData(id, data);
You do this for EVERY. SINGLE. ENDPOINT. It's a disaster waiting to happen. You'll miss one. You always do.
Row Level Security is a Postgres feature that lets you define access rules in the database schema. When enabled, your database automatically filters rows based on the current user.
Your application code becomes this:
// No middleware, no checks, just logic.
const { data, error } = await supabase
.from('profiles')
.update({ name: 'New Name' })
.eq('id', userId);
The database does the heavy lifting. If the user doesn't own the row, the update simply fails. Period.
Security stays next to the data. If you change your API, change your framework, or connect via a different tool, the security rules still apply.
Describe who can see what, instead of writing how to filter every query. It's readable, maintainable, and audit-friendly.
Postgres is optimized for this. When done correctly with indexes and proper helper functions, the overhead is negligible compared to the network latency of your bloated middleware.
Stop overthinking. These 3 patterns cover 90% of your needs.
The classic: each row belongs to a single user. Perfect for profiles, todos, and private notes.
-- Enable RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
-- Policy: Users can only see their own profiles
CREATE POLICY "Users can view own profile"
ON profiles FOR SELECT
TO authenticated
USING ( (select auth.uid()) = id );
Used for team-based apps where multiple users in one organization share data.
-- Policy: Users see data from their organization
CREATE POLICY "Team isolation"
ON projects FOR ALL
TO authenticated
USING (
org_id IN (
SELECT org_id FROM user_orgs
WHERE user_id = (select auth.uid())
)
);
Combine user ownership with admin privileges in a single rule.
-- Policy: Owner + Admin Hybrid
CREATE POLICY "Owner or Admin access"
ON documents FOR ALL
TO authenticated
USING (
auth.uid() = user_id
OR
(select auth.jwt()->>'role') = 'admin'
);
Hide deleted rows from normal users, but keep them in the DB for auditing.
-- Policy: Show only non-deleted rows
CREATE POLICY "Hide soft-deleted"
ON posts FOR SELECT
TO authenticated
USING ( deleted_at IS NULL );
-- Policy: Admins see everything
CREATE POLICY "Admins see deleted"
ON posts FOR SELECT
TO authenticated
USING ( (select auth.jwt()->>'role') = 'admin' );
Auditing RLS manually is a nightmare. Are you sure you didn't leave a table wide open? Are your policies optimized for performance?
SupaExplorer is the auditing platform for Supabase teams. We scan your database, identify leaks, and give you AI-powered recommendations in seconds.