How to Add Authentication to Your App Using Supabase

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

  1. Open SQL Editor in Supabase
  2. Click Create a new snippet
  3. Paste the SQL above
  4. 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.