Building a real app means handling user accounts. That means secure logins, password resets, email verification — all the boring-but-critical security work that’s easy to mess up. You could build it yourself. Or you could use Supabase’s built-in authentication system and get it right out of the box.
Supabase gives you everything you need for user management, so you can focus on what actually matters: your app.
How Supabase Authentication Works
When you create a Supabase project, you automatically get:
- A secure Users table — Accessed through Authentication → Users in your dashboard. This is Supabase’s locked-down, purpose-built table for login security only
- Sign-up, login, and password-reset flows — Pre-built and ready to use
- Email verification and security features — Out of the box, no extra work
That Users table only stores authentication data. It’s off-limits for anything else.
For your app-specific data — user names, avatars, preferences, settings — you create your own table in the public schema. This is where you store information that your app actually needs.
Why Create a Separate Profiles Table
Think of it this way:
- Users (auth) = your secure login system
- profiles (your table) = the user data your app needs
When someone signs up, both work together automatically. Supabase handles the security, and your profiles table stores everything else.
Step 1 — Copy This SQL Into Supabase
Here’s the complete SQL that creates your profiles table with Row Level Security (RLS), policies so users can only read/update their own data, and triggers for automatic profile creation:
-- Essential profile table schema for Supabase authentication
-- This works with Supabase's built-in auth.users table
-- Create profiles table
CREATE TABLE public.profiles (
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);
-- Enable Row Level Security
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
-- Create policies for profiles table
-- Users can view their own profile
CREATE POLICY "Users can view own profile" ON public.profiles
FOR SELECT USING (auth.uid() = id);
-- Users can update their own profile
CREATE POLICY "Users can update own profile" ON public.profiles
FOR UPDATE USING (auth.uid() = id);
-- Users can insert their own profile
CREATE POLICY "Users can insert own profile" ON public.profiles
FOR INSERT WITH CHECK (auth.uid() = id);
-- Function to automatically create profile when user signs up
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.profiles (id, email)
VALUES (new.id, new.email);
RETURN new;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Trigger to create profile on user signup
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();
-- Function to update updated_at timestamp
CREATE OR REPLACE FUNCTION public.handle_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = timezone('utc'::text, now());
RETURN new;
END;
$$ LANGUAGE plpgsql;
-- Trigger to update updated_at on profile changes
CREATE TRIGGER profiles_updated_at
BEFORE UPDATE ON public.profiles
FOR EACH ROW EXECUTE PROCEDURE public.handle_updated_at();
-- Optional: Create an index on email for faster lookups
CREATE INDEX profiles_email_idx ON public.profiles(email);
What is Row Level Security?
RLS is Supabase’s way of checking who’s asking on every read or write. Your policies are simple:
- SELECT — Users can only read their own row (where id = auth.uid())
- INSERT / UPDATE — Users can only create or change their own data
Result: no one can peek at or edit someone else’s data.
Step 2 — Run the SQL in Supabase
- Open SQL Editor in Supabase
- Click Create a new snippet
- Paste the SQL above
- Click RUN
Your profiles table will appear in Table Editor once it’s created.
Customizing Your Schema
Want more fields beyond the basics (like display_name, avatar_url, bio)? Paste this into Primio:
Modify this Supabase profile table SQL to add these fields: display_name TEXT, avatar_url TEXT, bio TEXT. Keep all security policies and triggers.
Primio will generate updated SQL with your custom fields. Copy-paste it into Supabase just like Step 1.
Step 3 — Build Your App with Primio
Now generate your Supabase auth app in Primio.
Here’s a prompt example (replace with your actual Supabase details):
Build a simple app with Supabase Auth that has:
1. A login page where users can sign up or log in with email and password using Supabase Auth
2. After login, show a welcome page with the user's email from their profile
3. Add a logout button
4. Make sure only logged-in users can see the welcome page
Keep it basic and clean.
Supabase details:
Project URL: https://your-project.supabase.co
Anon public key: your_anon_key_here
Primio generates your entire app — sign-up flow, login, logout, protected routes — all without you writing a single line of code.
You’re Done
That’s all it takes to add secure authentication to your app:
- Supabase handles the security — Rock-solid auth with zero effort
- Your profiles table stores user data — Flexible, exactly what your app needs
- Primio builds the UI — No code required
Try it now on primio.dev and see how fast you can go from idea to working app.