Supabase Auth is fundamentally a PostgreSQL database with a set of pre-built roles and row-level security (RLS) policies that manage user authentication and authorization.

Let’s see how this plays out with a quick example. Imagine you have a todos table. In Supabase, you’d create it like this:

create table public.todos (
  id bigserial primary key,
  user_id uuid references auth.users not null,
  title text,
  is_complete boolean default false,
  created_at timestamp with time zone default timezone('utc'::text, now())
);

Now, to make sure only the logged-in user can see their own todos, you’d enable RLS on the todos table:

alter table public.todos enable row level security;

And then define a policy:

create policy "Users can view their own todos"
on public.todos
for select using (auth.uid() = user_id);

create policy "Users can insert their own todos"
on public.todos
for insert with check (auth.uid() = user_id);

create policy "Users can update their own todos"
on public.todos
for update using (auth.uid() = user_id) with check (auth.uid() = user_id);

create policy "Users can delete their own todos"
on public.todos
for delete using (auth.uid() = user_id);

When a user logs in via Supabase Auth, they get a JWT. When your client application makes a request to Supabase, it includes this JWT. Supabase’s backend reads the JWT, extracts the user_id, and sets it as a session variable. When your SQL query hits the todos table, the auth.uid() function inside the RLS policy returns the user_id from the JWT, and the database ensures only rows where user_id matches the authenticated user’s ID are returned or modified. This means your backend doesn’t need to parse tokens or perform explicit authorization checks on every request; the database handles it at the data layer.

The core problem Supabase Auth solves is decoupling authentication from your application logic and centralizing authorization at the database level. Firebase Auth, on the other hand, often requires you to manage user data and permissions within your application’s backend or Firestore/Realtime Database rules, which can become complex and harder to manage as your application scales. Supabase’s approach leverages PostgreSQL’s robust security features, making authorization more declarative and less prone to errors.

For migrating your database from Firebase to Supabase, you’ll typically be moving from Firestore’s NoSQL document model to PostgreSQL’s relational model. This involves significant schema design changes.

Auth Migration:

Firebase Auth typically uses email/password, Google, GitHub, etc. Supabase Auth supports these and more, using JWTs. The migration path involves:

  1. Exporting Firebase Users: Use Firebase Admin SDK to export user data (email, UID, custom claims).
  2. Importing Users to Supabase: Use Supabase’s supabase.auth.admin.createUser() or importUser() methods. You’ll need to manually set passwords or use magic links for users who don’t have federated sign-in providers.
  3. Migrating Custom Claims: Map Firebase custom claims to Supabase’s auth.users.raw_app_meta_data or auth.users.user_metadata fields.

Database Migration:

Firebase uses Firestore (NoSQL document database). Supabase uses PostgreSQL (relational SQL database).

  1. Schema Design: Design your PostgreSQL schema. Think about tables, columns, data types, relationships (foreign keys), and indexes. This is the most critical step.
    • Firestore collections become PostgreSQL tables.
    • Document fields become table columns.
    • Nested documents or arrays might become separate tables with foreign key relationships.
  2. Data Export/Import:
    • Firestore Export: Use gcloud firestore export to export data to GCS, then download.
    • Data Transformation: Write scripts (e.g., Python with pandas) to transform the exported JSON data into a format suitable for PostgreSQL (e.g., CSV). You’ll need to flatten nested structures and handle data type conversions.
    • PostgreSQL Import: Use COPY command in psql or client libraries to import the transformed data.

Storage Migration:

Firebase Storage and Supabase Storage are conceptually similar (object storage).

  1. List Files: Use Firebase Admin SDK to list all files in your Firebase Storage buckets.
  2. Download Files: Download files from Firebase Storage.
  3. Upload Files: Use Supabase client libraries or CLI to upload files to your Supabase Storage buckets. You’ll need to manage metadata and access control (using RLS policies on storage.objects).

Example: Migrating a Simple User Profile

Let’s say you have a users collection in Firestore, with documents like:

{
  "uid": "firebase_user_id_123",
  "email": "user@example.com",
  "displayName": "Jane Doe",
  "createdAt": "2023-01-01T10:00:00Z"
}

In Supabase, you’d create a users table:

create table public.users (
  id uuid primary key default gen_random_uuid(), -- Match Supabase auth UUID
  firebase_uid text unique, -- To link back if needed
  email text unique,
  display_name text,
  created_at timestamp with time zone default timezone('utc'::text, now())
);

-- Enable RLS for the users table
alter table public.users enable row level security;

-- Policy to allow authenticated users to see their own profile
create policy "Users can view their own profile"
on public.users
for select using (auth.uid() = firebase_uid::uuid); -- Cast to uuid if firebase_uid is text

-- Policy to allow authenticated users to update their own profile
create policy "Users can update their own profile"
on public.users
for update using (auth.uid() = firebase_uid::uuid) with check (auth.uid() = firebase_uid::uuid);

When migrating, you’d export users from Firebase, and for each user, create a corresponding record in the Supabase users table. You would use the firebase_uid field to store the original Firebase UID for cross-referencing. The id field in Supabase would be the new primary key, and you’d ensure it’s set to auth.uid() when a new user signs up via Supabase Auth.

The counterintuitive part of migrating from a NoSQL document database like Firestore to a relational SQL database like PostgreSQL is that you often gain flexibility by introducing structure. While Firestore allows for schema-less flexibility where documents in a collection can have entirely different fields, PostgreSQL enforces a schema. This upfront discipline, however, leads to better data integrity, more powerful querying capabilities (especially for complex relationships), and easier management of data consistency across your application. It forces you to think critically about your data model, which is a net positive for most applications in the long run.

Once your data and auth are migrated, you’ll likely face challenges with real-time subscriptions, which Supabase offers via its Realtime Engine.

Want structured learning?

Take the full Supabase course →