Denis - Taganya.dev
← Back to Blog
Article·May 6, 2026

Building Secure User Authentication with Next.js & Supabase (Beginner to Intermediate)

From zero to production-ready auth: email verification, role-based access, JWT, refresh tokens, and the security mistakes most tutorials skip

Share:

Why Authentication Is Critical

Authentication is the front door of your application. Get it wrong and everything behind it is vulnerable — user data, payment info, business logic.

Most tutorials show you the happy path: user signs up, user logs in. This guide covers the full picture including what happens when things go wrong.

Firebase vs Supabase vs Custom — Which to Choose?

FeatureFirebaseSupabaseCustom
Setup time30 min30 min2-3 days
CostFree then expensiveFree tier generousCheap (just server)
ControlLowMediumFull
SQL supportNoYes (PostgreSQL)Yes
Best forQuick prototypesMost startupsLarge companies

My recommendation: Supabase for 90% of Tanzanian startups. It gives you a real PostgreSQL database, generous free tier, and excellent Next.js integration.

Step 1: Set Up Supabase

  1. Go to supabase.com and create a free account
  2. Create a new project (choose a server close to Tanzania — Mumbai or Frankfurt)
  3. Go to Settings → API and copy your Project URL and anon public key

Create a .env.local file in your project root:

NEXT_PUBLIC_SUPABASE_URL=your_project_url_here
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key_here

Important: Never commit .env.local to GitHub. Add it to .gitignore.

Step 2: Install and Configure Supabase Client

npm install @supabase/supabase-js @supabase/ssr

Create lib/supabase.ts:

import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

Step 3: Sign Up with Email Verification

const supabase = createClient()

async function signUp(email: string, password: string) {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
    options: {
      emailRedirectTo: `${window.location.origin}/auth/callback`
    }
  })

  if (error) throw error
  return data
}

Supabase sends the verification email automatically — no extra configuration needed on your side.

Step 4: Sign In and Session Management

async function signIn(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password
  })

  if (error) throw error
  return data.session
}

Supabase handles JWT refresh tokens automatically. The session persists across page reloads.

Step 5: Protecting Pages with Middleware

Create middleware.ts in your project root:

import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
  const response = NextResponse.next()

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    { cookies: { /* cookie handlers */ } }
  )

  const { data: { user } } = await supabase.auth.getUser()

  if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return response
}

export const config = {
  matcher: ['/dashboard/:path*']
}

Step 6: Role-Based Access Control

In Supabase, add a role column to your users table:

ALTER TABLE auth.users ADD COLUMN role TEXT DEFAULT 'user';

Then check the role in your API routes:

const { data: { user } } = await supabase.auth.getUser()
const userRole = user?.user_metadata?.role

if (userRole !== 'admin') {
  return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}

Security Best Practices

  1. Always validate on the server, not just the client — client-side checks are for UX only
  2. Use Row Level Security in Supabase — users can only access their own data
  3. Rate limit your auth endpoints — limit to 5 failed attempts per IP per minute
  4. Keep access tokens short-lived — Supabase default of 1 hour is fine
  5. Validate email domains for B2B apps — only allow company email addresses during signup

Common Vulnerabilities to Avoid

  • Never store passwords in plain text — Supabase handles hashing automatically
  • Never trust user-provided role claims — always read role from your database
  • Never skip email verification — it prevents fake accounts and spam
  • Never expose your service role key — only use the anon key on the frontend

Conclusion

Supabase gives you production-ready authentication in under an hour — but only if you configure it correctly. The steps above cover everything you need for a real application with real users.

The security section is not optional. Every point there is something I have seen exploited in Tanzanian apps in the last two years.

Want me to review your authentication setup? Book a free consultation — I will check it against this checklist and give you specific improvements.

Get new articles by email

A short email whenever I publish something new. No spam, unsubscribe anytime.

Found this useful?

If you need help applying any of this to your own project, I offer free 30-minute consultations.