I have the following table called "profile":
id, name, created_at
Every user ( with SupaBase ) has his own row in the "profile" table with his user_id as the key ( id )
I added RLS so that only the user can update/delete his own row but everyone else can only select the row ( anyone can see his profile )
This works great but the issue is that someone could query every single user in the table using SELECT * FROM profile
or something similar. I only want people to be able to view a row if they already have their "id" or their "name" with a WHERE
statement.
The only solution I see is to remove the ability to SELECT the table ( basically make it private ) and make an API that will query with the admin key ( bypasses the RLS ). That way, no one could query the whole table and the client would just call the API which would then query the data.
However, I would like to query directly from the client so I want to know if there is a different solution that doesn’t require an API in between. Currently learning Postgresql so I’m probably missing something really simple.
3
Answers
See Row Security Policies page, which has your exact scenario as an example:
You cannot obtain the underlining query within your row level security policy, so the exact solution that you are looking for cannot be done, but there is a workaround.
You can deny all access for selecting user, and then create the following database function.
security definer
at the end means that this function will bypass row level security.With the above function in place, you can query the data like this from your client:
With this, anyone can only query the user if they know their user_id, and you can still query the user directly from the client.
I had the exact same problem in several tables, and I wanted to use the supabase database client, to mantain the ability to query, sort, paginate, etc.
Looking at the supabase documentation, I saw that you could access the request headers at the RLS, with a function (https://supabase.com/docs/learn/auth-deep-dive/auth-policies, user_agent example to access the browser user-agent)
So, I create a function to get a custom header element, in my case named "query_data":
In the tables with user_id, I add the following RLS for select:
So, with this elements, I can query with the supabase client, only adding in the header the parameter ‘query-data’ with the id of the user I want to select, and I can still use aditional queries, paginate the results, sort, etc.
The only thing I miss if that I need to create a new supabase client each time, to pass the proper headings, like so:
I don’t know how to change the headers dynamically.