Originally published on clintech.me
When you move from toy projects to real products, the hard problem
isn't styling cards. It's isolation: how do you make sure each user
only sees their own data?
The wrong answer: filter by userId in React and hope no one cheats.
The right answer: Row-Level Security.
What RLS actually is
RLS is a Postgres feature that enforces access policies at the database
level — not the application level. Every query runs through your
policies before returning data.
CREATE POLICY "Users can see their own jobs"
ON jobs FOR SELECT
USING (user_id = auth.uid());
With this in place, even if someone opens DevTools and calls the
Supabase REST API directly with a valid token, they still only get rows
where user_id matches their own auth UID. The database is the
security gate, not your frontend.
How I applied this in ApplyCraft
ApplyCraft is an AI-powered job
tracker I built. Every job, note, and profile is scoped per user.
The policy pattern is consistent across all tables:
jobs.user_id = auth.uid()job_notes.user_id = auth.uid()profiles.id = auth.uid()
Even the server-side AI endpoint, which writes outreach copy back
to a job row, verifies ownership before touching the database.
No client-side key exposure, no trust in the frontend.
The mistakes that will burn you
1. Enabling RLS but leaving "allow all" policies
You've technically turned it on but effectively disabled it.
Every policy needs to be explicit.
2. Forgetting write policies
Easy to add SELECT and forget INSERT, UPDATE, DELETE.
Your app will silently fail on writes and you'll spend an hour
wondering why.
3. Trusting only frontend filters
If a table has unrestricted SELECT, any user can bypass your
UI and call the REST endpoint directly. RLS removes that
possibility entirely.
4. Not following the ownership chain on joins
If notes belongs to jobs and jobs belongs to a user, your
note policies should check ownership through that chain, not
just assume the foreign key is enough.
The difference between "multi-user UI" and "multi-tenant product"
is where security lives. Once the database enforces it, your
frontend gets simpler and your app gets much harder to abuse.
I'm a frontend engineer open to remote EU roles, connect with
me on LinkedIn
if this was useful.
Top comments (0)