Supabase Authentication With supabase-js v2

Supabase Auth signs people in with email and password, one-time magic links, phone codes or OAuth providers such as GitHub, all through the supabase.auth object in supabase-js. Each sign-in yields a session whose token reaches your database, where row level security policies compare auth.uid() with a user_id column to decide what that person may read.

Sign-in methods and the call for each
Methodsupabase-js v2 callGood for
Email and passwordauth.signUp / auth.signInWithPasswordClassic accounts
Magic linkauth.signInWithOtp({ email })No passwords to forget
SMS or WhatsApp codeauth.signInWithOtp({ phone })Mobile-first products
OAuth providerauth.signInWithOAuth({ provider })One-click sign-in with an existing account
Sign outauth.signOut()Ending the session

Updated

Auth is the part of a Supabase project people most often wire up wrong: the sign-in works, but the data is wide open or the server trusts a cookie it should not. Pick a method below to get its code, then follow the policies section so each person only sees their own rows.

Code for each sign-in method

Email and password

Create the account, then sign in. With confirmation on, data.session is null until the email link is clicked.

import { createClient } from "@supabase/supabase-js";
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

const { data, error } = await supabase.auth.signUp({
  email: "[email protected]",
  password: "a-long-unique-passphrase",
  options: { emailRedirectTo: "https://example.com/welcome" },
});

const { data: signedIn, error: signInError } = await supabase.auth.signInWithPassword({
  email: "[email protected]",
  password: "a-long-unique-passphrase",
});

Policies that use auth.uid()

Each person reads and writes only their own notes

Run in the SQL editor. The default fills user_id from the token, and with check stops anyone inserting rows for another user.

create table public.notes (
  id bigint generated always as identity primary key,
  user_id uuid not null default auth.uid() references auth.users (id) on delete cascade,
  body text not null
);

alter table public.notes enable row level security;

create policy "read own notes" on public.notes
  for select to authenticated
  using ((select auth.uid()) = user_id);

create policy "add own notes" on public.notes
  for insert to authenticated
  with check ((select auth.uid()) = user_id);

create policy "edit own notes" on public.notes
  for update to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);

create policy "delete own notes" on public.notes
  for delete to authenticated
  using ((select auth.uid()) = user_id);

Sessions and tokens

After sign-in, supabase-js stores a short-lived access token and a refresh token, and refreshes the access token automatically. Every query you make through the same client sends the access token, which is how Postgres learns the user's ID.

In server-rendered apps the session lives in cookies instead. The Next.js authentication guide on this site covers that wiring, so this page stays with the client library and the database rules.

Row level security is the real lock

Hiding a button in the interface does not protect data, since anyone can call your API with the public key. Policies run inside Postgres on every request, which makes them the place where ownership is enforced.

Write one policy per action. A select policy uses using, while insert needs with check so people cannot create rows owned by someone else, and update usually needs both.

How it works

  1. Pick a sign-in method in the code picker and copy the snippet into your app.
  2. Turn the matching provider on in the Supabase dashboard and add your site's address to the allowed redirect URLs.
  3. Listen for auth events so your interface updates when someone signs in or out.
  4. Enable row level security on every table that holds personal data and add policies that use auth.uid().

Tips

Frequently asked questions

Is Supabase magic link the same as OTP?

Both start from signInWithOtp. For email, the message carries a link when the template uses the confirmation URL and a code when it uses the token. Phone sign-in only supports codes, by SMS or WhatsApp.

How do I add Google or GitHub sign-in?

Enable the provider in the dashboard with the client ID and secret from that provider, then call signInWithOAuth with its name and an optional redirectTo address. The person returns to your site already signed in.

How do I know when a user signs in or out?

Register onAuthStateChange. It reports events including INITIAL_SESSION, SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, USER_UPDATED and PASSWORD_RECOVERY, and returns a subscription you unsubscribe from when the component unmounts.

Why can signed-in users see nothing after I enabled RLS?

Once row level security is on, a table denies everything until a policy allows it. Add a select policy for the authenticated role that compares (select auth.uid()) with the owner column.

Should I use Supabase Auth or Firebase Auth?

Both cover the common methods. The bigger difference is the database underneath; our Supabase vs Firebase comparison goes through that choice in detail.

Related: Supabase vs Firebase, Next.js authentication, Authentication basics, Accept payments on a website.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap