Denis - Taganya.devBook Free Consultation
← 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

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.

Found this useful?

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