Auth Made Easy: Complete Authentication with Supabase

Kevin Dávila
A confession: I hate setting up authentication. OAuth redirects, JWTs expiring at the worst possible moment, sessions lost between tabs, and that login bug that only shows up on a Sunday at midnight.
That's why I couldn't believe it when Supabase Auth had login with Google, GitHub, and email/password working in literally 15 lines of code. Here you'll put together complete authentication, from the basics to MFA, with no drama.
Supabase Auth Stack
Supabase uses GoTrue as its authentication service. Here's what it offers:
Email/Password
Social OAuth (Google, GitHub, Apple, Discord, etc.)
Magic Link (passwordless login)
Phone/SMS
MFA (Multi-Factor Authentication)
All of this is included in the free tier, with up to 50,000 users.
Initial setup
Environment variables
// .env
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Supabase client
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseKey)Email and Password
Sign up
// src/auth/signup.ts
import { supabase } from '../lib/supabase'
export async function signUp(email: string, password: string) {
const { data, error } = await supabase.auth.signUp({
email,
password,
})
if (error) throw error
return data
}Sign in
// src/auth/login.ts
import { supabase } from '../lib/supabase'
export async function signIn(email: string, password: string) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) throw error
return data
}Sign out
// src/auth/logout.ts
import { supabase } from '../lib/supabase'
export async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) throw error
}Social OAuth
Setting up login with Google or GitHub is trivial. You just need to add OAuth credentials in the Supabase dashboard.
// src/auth/google.ts
import { supabase } from '../lib/supabase'
export async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) throw error
return data
}GitHub
// src/auth/github.ts
import { supabase } from '../lib/supabase'
export async function signInWithGitHub() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) throw error
return data
}Configure OAuth providers
In the Supabase dashboard:
Go to Authentication > Providers
Select Google or GitHub
Add your Client ID and Client Secret
Copy the callback URL Supabase gives you
Configure that URL in the provider's console (Google Cloud Console, GitHub Settings)
Magic Link
Passwordless login, just a link to your email:
// src/auth/magic-link.ts
import { supabase } from '../lib/supabase'
export async function signInWithMagicLink(email: string) {
const { data, error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) throw error
return data
}Listen to session changes
// src/auth/listener.ts
import { supabase } from '../lib/supabase'
// Listen to auth changes (login, logout, token refresh)
supabase.auth.onAuthStateChange((event, session) => {
console.log('Auth event:', event)
console.log('User:', session?.user)
// Possible events:
// - SIGNED_IN
// - SIGNED_OUT
// - TOKEN_REFRESHED
// - USER_UPDATED
// - PASSWORD_RECOVERY
})Get current user
// src/auth/user.ts
import { supabase } from '../lib/supabase'
export async function getCurrentUser() {
const { data: { user }, error } = await supabase.auth.getUser()
if (error) throw error
return user
}MFA (Multi-Factor Authentication)
For apps that need extra security, Supabase supports TOTP (Time-based One-Time Password):
// src/auth/mfa.ts
import { supabase } from '../lib/supabase'
// Enroll MFA
export async function enrollMFA() {
const { data, error } = await supabase.auth.mfa.enroll({
factorType: 'totp',
friendlyName: 'My App'
})
if (error) throw error
// data.totp.qr_code - QR to scan with authenticator app
// data.totp.secret - Secret to enter manually
return data
}
// Verify MFA
export async function verifyMFA(factorId: string, challengeId: string, code: string) {
const { data, error } = await supabase.auth.mfa.verify({
factorId,
challengeId,
code
})
if (error) throw error
return data
}Protect routes on the client
With vanilla JS
// src/auth/guard.ts
import { supabase } from '../lib/supabase'
export async function requireAuth() {
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
window.location.href = '/login'
return null
}
return session
}With Angular
// src/app/guards/auth.guard.ts
import { inject } from '@angular/core'
import { CanActivateFn, Router } from '@angular/router'
import { SupabaseService } from '../services/supabase.service'
export const authGuard: CanActivateFn = async () => {
const supabase = inject(SupabaseService)
const router = inject(Router)
const { data: { session } } = await supabase.client.auth.getSession()
if (!session) {
router.navigate(['/login'])
return false
}
return true
}Dashboard Configuration
Some important settings in Authentication > Settings:
Site URL: Your app URL (e.g., http://localhost:5173)
Redirect URLs: Allowed URLs after login
Email confirmations: Enable/disable email confirmation
Password requirements: Minimum length, special characters
Common errors
"Invalid login credentials"
Verify the user exists
Check that the email is confirmed (if you have confirmation enabled)
"Redirect URL not allowed"
Add the URL in Authentication > URL Configuration
Include both localhost and your production domain
Token expired
Supabase handles refresh tokens automatically
Verify that autoRefreshToken is enabled (default)
What's next
In the next article we cover RLS (Row Level Security): how to protect your data directly in the database, without relying only on client code. It's the piece that makes Supabase secure for production.