Every time I started a new project, auth killed my momentum.
Setting up JWT, hashing passwords, managing sessions,
handling refresh tokens... before writing a single line
of actual product code I'd already wasted 2 days.
Then I found Supabase Auth. I had a fully working
login system in my Next.js app in 10 minutes flat.
Here's exactly how.
Why auth is such a pain normally
If you've ever built auth from scratch you know the drill:
- Install bcrypt, hash every password manually
- Set up JWT, handle token expiry, refresh tokens
- Store sessions, manage cookies securely
- Handle edge cases like "what if the token expires mid-session"
- Pray nothing is vulnerable
It's not that it's impossible. It's that it's boring,
repetitive work that has nothing to do with your
actual product.
Supabase handles ALL of that for you.
You just call a function. That's it.
What we're building
- Email/password authentication
- Protected routes
- Session management
- Sign in, Sign up, Sign out
Prerequisites
- Next.js app set up
- Supabase account (free at supabase.com)
- Basic knowledge of React
Step 1 — Create a Supabase project
- Go to supabase.com
- Click New Project
- Fill in your project name and database password
- Wait for it to spin up (~2 minutes)
Once ready, go to Settings → API and copy:
Project URL-
anon publickey
Step 2 — Install Supabase in your Next.js app
npm install @supabase/supabase-js @supabase/ssr
Step 3 — Set up environment variables
Create a .env.local file in your root:
NEXT_PUBLIC_SUPABASE_URL=your_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
Don't commit this to GitHub. Make sure .env.local
is in your .gitignore — if it's not, add it now.
Step 4 — Create the Supabase client
Create lib/supabase.js:
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)
}
I keep this in a lib folder so I can import it
anywhere in the app without repeating myself.
Step 5 — Sign up page
Create app/signup/page.jsx:
'use client'
import { useState } from 'react'
import { createClient } from '@/lib/supabase'
import { useRouter } from 'next/navigation'
export default function SignUp() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const router = useRouter()
const supabase = createClient()
const handleSignUp = async () => {
const { error } = await supabase.auth.signUp({ email, password })
if (error) {
setError(error.message)
} else {
router.push('/dashboard')
}
}
return (
<div>
<h1>Create account</h1>
<input
type="email"
placeholder="your@email.com"
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="password"
onChange={(e) => setPassword(e.target.value)}
/>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button onClick={handleSignUp}>Sign Up</button>
</div>
)
}
Nothing fancy. Grab email and password, pass it to
Supabase, redirect to dashboard. Done.
Step 6 — Sign in page
Create app/signin/page.jsx:
'use client'
import { useState } from 'react'
import { createClient } from '@/lib/supabase'
import { useRouter } from 'next/navigation'
export default function SignIn() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const router = useRouter()
const supabase = createClient()
const handleSignIn = async () => {
const { error } = await supabase.auth.signInWithPassword({
email,
password
})
if (error) {
setError(error.message)
} else {
router.push('/dashboard')
}
}
return (
<div>
<h1>Welcome back</h1>
<input
type="email"
placeholder="your@email.com"
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="password"
onChange={(e) => setPassword(e.target.value)}
/>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button onClick={handleSignIn}>Sign In</button>
</div>
)
}
Quick note — make sure you use
signInWithPassword
notsignIn. I spent 20 minutes confused the first
time because of this 😅 Supabase has multiple sign
in methods so you have to be specific.
Step 7 — Protect your dashboard
This is the part most tutorials skip completely.
What's the point of auth if anyone can just
go to /dashboard directly in the URL bar?
Create app/dashboard/page.jsx:
import { createClient } from '@/lib/supabase'
import { redirect } from 'next/navigation'
export default async function Dashboard() {
const supabase = createClient()
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
redirect('/signin')
}
return (
<div>
<h1>Hey {session.user.email} 👋</h1>
<p>You're in.</p>
</div>
)
}
No session? Get out. Simple as that.
Step 8 — Sign out
Add this wherever makes sense in your app:
const supabase = createClient()
const handleSignOut = async () => {
await supabase.auth.signOut()
router.push('/signin')
}
<button onClick={handleSignOut}>Sign out</button>
You just saved yourself 2 days
Most developers waste hours on auth before
writing a single feature. You just skipped
all of that in 10 minutes.
Your users can now sign up, log in, and access
protected pages. That's a real app.
Now go build the actual product.
In under 10 minutes you now have:
- ✅ Email/password sign up
- ✅ Sign in with session management
- ✅ Protected dashboard route
- ✅ Sign out functionality
The bigger picture
Auth is just the beginning of what Supabase can do.
Once your users can log in you've got a real app —
now you can start building the actual product.
In my next article I'll show you how to:
- Add Google OAuth in 3 lines
- Create a user profile table that auto-links to auth
- Handle protected API routes in Next.js
If you're building a SaaS product and feeling stuck,
I write about real problems I run into while building
with Next.js and Supabase.
I'm Krishna — full stack dev from Kathmandu, Nepal.
I've been building SaaS products and helping clients
turn ideas into real web apps.
Got a project in mind? Let's talk →
krishna011.com.np
Top comments (0)