RLS Simplified
Last edited: 1/18/2025
TL;DR: Basic summary
Row-Level Security (RLS) Policy: A WHERE
or CHECK
condition applied automatically to database queries
Key features:
- Applies without being explicitly added to each query, which makes it good for policing row access from unknown entities, such as those leveraging the anon or authenticated roles.
- Can be set for specific actions (e.g., SELECT, INSERT)
- Can target particular database roles (e.g., "anon", "authenticated")
Contrast with regular conditions:
- Regular conditions: Apply to all roles and must be added manually to each query
- RLS policies: Applied automatically to specified actions and roles
Hands on walk-through for conditions
USING:
The USING
keyword inspects the value of row to see if it should be made visible to the query.
When you SELECT, UPDATE, or DELETE, you have to use a WHERE statement to search for specific rows:
_13-- select_13select *_13from some_table_13where id = 5;_13_13-- update_13update some_table_13set id = 6_13where id = 5;_13_13-- delete_13delete from some_table_13where id = 6;
Even when you don't use a WHERE statement, there's still an implicit one:
_10-- ...your query_10WHERE true;
The USING
clause appends more to the WHERE statement:
_15-- Your Using condition_15USING (_15 (select auth.uid()) = user_id_15);_15_15-- Your query without RLS:_15SELECT * FROM some_table_15WHERE id = 5 OR id = 6;_15_15-- Your query after RLS_15SELECT * FROM some_table_15WHERE_15 (id = 5 OR id = 6)_15 AND_15 (select auth.uid()) = user_id) -- <--- added by the USING clause;
WITH CHECK:
Let's say you have a profile table. Well, you don't want user's to be able to modify their user_id when they make an insert, do you?
The WITH CHECK
condition inspects values that are being added or modified. For INSERT you'd use it by itself. There's no need for a using clause:
_18-- Allow users to add to table, but make sure their user_id matches the one in their JWT:_18_18create policy "Allow user to add posts"_18on "public"."posts"_18as PERMISSIVE_18for INSERT_18to authenticated_18with check(_18 (select auth.uid()) = user_id_18);_18_18-- Example: failing insert_18INSERT INTO posts_18VALUES (<false id>, <comment>);_18_18-- Example: successful insert_18INSERT INTO posts_18VALUES (<real id>, <comment>);
INSERTs do not rely on WHERE clauses, but they can have constraints. In this case, the RLS acts as a CHECK constraint against a column, e.g.:
_10ALTER TABLE table_name_10ADD CONSTRAINT constraint_name CHECK (condition);
What distinguishes it from normal CHECK
constraints is that it is only activate for certain roles or methods.
UPDATEs:
UPDATE both filters for rows to change and then adds new values to the table, so it requires both USING and WITH CHECK conditions:
_11create policy "Allow user to edit their stuff"_11on "public"."<SOME TABLE NAME>"_11as RESTRICTIVE_11for UPDATE_11to authenticated_11using (_11 (select auth.uid()) = user_id_11)_11with check(_11 (select auth.uid()) = user_id_11);