Row Level Security in the Next.js Supabase Turbo kit
Learn how to secure your Next.js application with Row Level Security (RLS) using the Next.js Supabase Turbo kit.
If you've opted to using the Data API in your application, you can use Row Level Security (RLS) to secure your data (which Makerkit does by default).
Steps to secure your data with RLS
Learn how to secure your data with RLS using the Next.js Supabase Turbo kit.
The general rule of thumb is that you must always ensure RLS is enabled for your tables. Failure to do so will result in a security vulnerability because the table will be exposed to the public - and everyone will be able to read and write to it. You don't want that.
Supabase has a great guide on how to use RLS - don't skip it!
Enable RLS for your tables
When you write your table migrations, you must ensure that RLS is enabled for the table.
First, create a table with the following command:
create table if not exists public.documents ( id uuid primary key default uuid_generate_v4(), user_id uuid not null references auth.users(id), title text not null, content text not null, created_at timestamp with time zone default now(), updated_at timestamp with time zone default now());
Enable RLS for the table
Now, you need to enable RLS for the table.
alter table public.documents enable row level security;
PS: you can also enable RLS for the table using the Supabase Studio - however I am not sure I'd recommend this approach.
Add the RLS policy for restricted access
Now that we have a table with RLS enabled, and no policies added, you will notice that you won't be able to read or write to the table. This is good, because it means that the table is secure. However, we want to be able to read and write to the table for the users that are authorized to do so.
To do this, we need to add a policy to the table.
create policy "Users can view their own documents" on public.documents to authenticated for select using ((select auth.uid()) = user_id);
This policy will allow any authenticated user to read their own documents - e.g. the documents whose user_id
matches the authenticated user's ID.
If you wanted to allow all actions at once, you could use the following policy:
create policy "Users can view their own documents" on public.documents to authenticated for all using ((select auth.uid()) = user_id);
Using Makerkit's Functions to write RLS policies
Makerkit comes with a set of functions that make it easier to write RLS policies. Below are some of the most common use cases:
Verifying a user is part of a Team Account
If you want to verify that a user is part of a Team Account, you can use the public.has_role_on_account
function.
create policy "Users can view their account's documents" on public.documents to authenticated for select using (public.has_role_on_account(account_id));
If you want to check for a specific role, you can do so by passing the role name to the function.
create policy "Users can view their account's documents" on public.documents to authenticated for select using (public.has_role_on_account(account_id, 'admin'));
NB: we're expecting the public.documents
table to have an account_id
column that references the public.accounts
table.
Verifying a user has the required permissions
If you want to verify that a user has the required permissions, you can use the public.has_permission
function.
create policy "Users can view their account's documents" on public.documents to authenticated for select using (public.has_permission(auth.uid(), account_id, 'documents.read'));
Verifying a user is the owner of a Team Account
If you want to verify that a user is the owner of a Team Account, you can use the public.is_owner
function.
create policy "Users can view their account's documents" on public.documents to authenticated for select using (public.is_account_owner(account_id));
These are just some of the most common use cases and will likely cover the vast majority of your RLS policies.
Testing RLS policies
We have two different ways to test RLS policies:
- Manually: Using the Supabase Studio impersonation feature
- Automatically: Using pgTap tests
Manually testing RLS policies
To test RLS policies manually, you can use the Supabase Studio impersonation feature.
- Go to the Supabase Studio
- Navigate to the Table you want to test
- Impersonate a user
- Try to read, write or delete data using the UI or the SQL editor
- Verify that the data is restricted based on the RLS policy
Automatically testing RLS policies
To test RLS policies automatically, you can use the supabase test db
command. This command uses pgTap to test the RLS policies - which is an invaluable tool for testing RLS policies in an automated and repeatable way.
We have a set of tests in the supabase/tests/database
folder that are designed to test the RLS policies. You can copy the same structure and add your own tests.
Here's an example of a test:
-- authenticate with a userselect tests.create_supabase_user('testuser', 'testuser@test.com');select tests.create_supabase_user('testuser2', 'testuser2@test.com');-- authenticate as the first userselect tests.authenticate_as('testuser');-- create a documentinsert into public.documents (user_id, title, content)values (tests.get_supabase_uid('testuser'), 'Test Document', 'This is a test document');-- test the user can read their own documentselect row_eq( $$ SELECT * from public.documents $$, row(tests.get_supabase_uid('testuser'), 'Test Document', 'This is a test document'), 'User should be able to read their own document');-- alternatively, check the list is not emptyselect not_empty( $$ SELECT * from public.documents $$, 'User should be able to read their own document');-- authenticate with another userselect tests.authenticate_as('testuser2');-- test that the document is not visible to the other userselect is_empty( $$ SELECT * from public.documents $$, 'No documents should be visible to unauthenticated users');
This is a simple example, but you can see how you can test the RLS policies for different scenarios.