Authentication Methods
The idea in one sentence
Section titled “The idea in one sentence”Supabase Auth is built on the open-source GoTrue server and supports several ways for a user to prove who they are — password, magic link, phone OTP, or OAuth — and every one of them ends the same way: a row is created or matched in the built-in auth.users table.
The main sign-in methods
Section titled “The main sign-in methods”supabase-js exposes each sign-in method as a method on supabase.auth. Email and password is the classic flow. A magic link sends the user a one-time sign-in link by email instead of asking for a password — the user clicks it and is signed in, nothing to type or forget. Phone OTP works the same way but delivers a one-time code by SMS. OAuth hands the user off to a third-party provider (Google, GitHub, and others) and Supabase handles the redirect dance and token exchange for you.
import { createClient } from '@supabase/supabase-js';
const supabase = createClient( process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!);
// Email + passwordconst { data, error } = await supabase.auth.signUp({ password: 'correct-horse-battery-staple',});
// Magic link — no password, just a one-time sign-in link emailed to the userconst { error: otpError } = await supabase.auth.signInWithOtp({});
// OAuth — Supabase handles the redirect dance with the provider for youconst { error: oauthError } = await supabase.auth.signInWithOAuth({ provider: 'github',});Where the user actually lives: auth.users
Section titled “Where the user actually lives: auth.users”Every sign-up or sign-in, regardless of method, creates or authenticates a row in auth.users. That table lives in a separate auth schema, not public — it is managed by Supabase Auth itself and holds only what Auth needs (email, phone, hashed password, provider metadata, and so on). It is not meant to be extended directly with your own app columns.
The common pattern is a public.profiles table whose primary key references auth.users(id), holding whatever app-specific data Auth itself does not store — a username, an avatar URL, a subscription tier, anything your product needs.
-- public.profiles holds app-specific data auth.users does not storecreate table public.profiles ( id uuid primary key references auth.users (id) on delete cascade, username text unique, avatar_url text, created_at timestamptz default now());A trigger on auth.users that inserts a matching public.profiles row on sign-up is a common way to keep the two in sync, though the details of that trigger are outside this lesson.
flowchart LR
user["User"] --> pw["Email + password"]
user --> magic["Magic link"]
user --> otp["Phone OTP"]
user --> oauth["OAuth provider"]
pw --> gotrue["Supabase Auth (GoTrue)"]
magic --> gotrue
otp --> gotrue
oauth --> gotrue
gotrue --> table[("auth.users row created/matched")]