--- id: managing-user-data title: Managing User Data description: Securing your user data with Row Level Security. --- For security purposes, the `auth` schema is not exposed on the auto-generated API. Even though Supabase provides an `auth.users` table, it can be helpful to create tables in the `public` schema for storing user data that you want to access via the API. ## Creating user tables When you create tables to store user data, it's helpful to reference the `auth.users` table in the primary key. This ensures data integrity. For example, a `public.profiles` table might look like this: ```sql create table public.profiles ( id uuid references auth.users not null, first_name text, last_name text, primary key (id) ); alter table public.profiles enable row level security; ``` ## Public access Since Row Level Security is enabled, this table is accessible via the API but no data will be returned unless we set up some Policies. If we wanted the data to be _readable_ by everyone but only allow logged-in users to update their own data, the Policies would look like this: ```sql create policy "Public profiles are viewable by everyone." on profiles for select using ( true ); create policy "Users can insert their own profile." on profiles for insert with check ( auth.uid() = id ); create policy "Users can update own profile." on profiles for update using ( auth.uid() = id ); ``` ## Private access If the data should only be _readable_ by the user who owns the data, we just need to change the `for select` query above. ```sql create policy "Profiles are viewable by users who created them." on profiles for select using ( auth.uid() = id ); ``` The nice thing about this pattern? We can now query this table via the API and we don't need to include data filters in our API queries - the Policies will handle that for us: ```js // This will return nothing while the user is logged out const { data } = await supabase .from('profiles') .select('id, username, avatar_url, website') // After the user is logged in, this will only return // the logged-in user's data - in this case a single row const { error } = await supabase.auth.signIn({ email }) const { data: profile } = await supabase .from('profiles') .select('id, username, avatar_url, website') ``` ## Bypassing Row Level Security If you need to fetch a full list of user profiles, we supply a `service_key` which you can use with your API and Client Libraries to bypass Row Level Security. Make sure you _NEVER_ expose this publicly. But it can be used on the server-side to fetch all of the profiles. ## Advanced techniques ### Using triggers If you want to add a row to your `public.profiles` table every time a user signs up, you can use triggers. If the trigger fails however, it could block the user sign ups - so make sure that the code is well-tested. For example: ```sql -- inserts a row into public.users create function public.handle_new_user() returns trigger language plpgsql security definer set search_path = public as $$ begin insert into public.profiles (id) values (new.id); return new; end; $$; -- trigger the function every time a user is created create trigger on_auth_user_created after insert on auth.users for each row execute procedure public.handle_new_user(); ```