Guides

Authentication & Authorization Patterns

Implementing auth with Supabase, Clerk, and NextAuth — protecting routes, managing sessions, and role-based access control.

Authentication & Authorization Patterns

Implementing auth with Supabase, Clerk, and NextAuth — protecting routes, managing sessions, and role-based access control.

Authentication vs Authorization

  • Authentication (AuthN): Verifying who the user is (login, signup, password reset)
  • Authorization (AuthZ): Determining what they can do (roles, permissions, access control)

You need both. Authentication without authorization means every logged-in user can do everything.

Supabase Auth

Supabase Auth is built on GoTrue and integrates directly with your Supabase database.

Setup with Next.js

Install the packages:

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

Create the Supabase clients:

Server client (lib/supabase/server.ts):

import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() { return cookieStore.getAll() },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          )
        },
      },
    }
  )
}

Browser client (lib/supabase/client.ts):

import { createBrowserClient } from '@supabase/ssr'

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

Protecting Routes with Middleware

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

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

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() { return request.cookies.getAll() },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) => {
            request.cookies.set(name, value)
            response.cookies.set(name, value, options)
          })
        },
      },
    }
  )

  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*', '/settings/:path*'],
}

Email/Password Auth

// Sign up
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  options: {
    data: { full_name: 'Jane Doe' }
  }
})

// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password',
})

// Sign out
await supabase.auth.signOut()

// Get current user (server-side — always use getUser, not getSession)
const { data: { user } } = await supabase.auth.getUser()

OAuth (Social Login)

const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: `${window.location.origin}/auth/callback`,
  },
})

Create the callback route (app/auth/callback/route.ts):

import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get('code')

  if (code) {
    const supabase = await createClient()
    await supabase.auth.exchangeCodeForSession(code)
  }

  return NextResponse.redirect(`${origin}/dashboard`)
}

Clerk

Clerk provides pre-built UI components and handles the entire auth flow.

Setup with Next.js

npm install @clerk/nextjs

Add middleware (middleware.ts):

import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)'])

export default clerkMiddleware(async (auth, req) => {
  if (isProtectedRoute(req)) await auth.protect()
})

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
}

Use the pre-built components:

import { SignIn, SignUp, UserButton } from '@clerk/nextjs'

// In your header
<UserButton afterSignOutUrl="/" />

// Sign in page
<SignIn />

// Sign up page
<SignUp />

Role-Based Access Control (RBAC)

Database-Level RBAC

Store roles in your database:

create table public.user_roles (
  user_id uuid references auth.users(id) on delete cascade,
  role text not null check (role in ('user', 'admin', 'super_admin')),
  primary key (user_id, role)
);

Check roles in RLS policies:

create policy "Admins can view all data"
  on public.bookmarks for select
  using (
    exists (
      select 1 from public.user_roles
      where user_id = auth.uid() and role = 'admin'
    )
  );

Application-Level RBAC

Create a utility function:

// lib/auth.ts
import { createClient } from '@/lib/supabase/server'

export async function getUserRole() {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) return null

  const { data } = await supabase
    .from('user_roles')
    .select('role')
    .eq('user_id', user.id)
    .single()

  return data?.role ?? 'user'
}

export async function requireRole(role: string) {
  const userRole = await getUserRole()
  if (userRole !== role) {
    throw new Error('Unauthorized')
  }
}

Use in server components or API routes:

// app/admin/page.tsx
import { requireRole } from '@/lib/auth'

export default async function AdminPage() {
  await requireRole('admin')
  // ... render admin page
}

Security Checklist

  • Use HTTPS everywhere (Vercel handles this)
  • Store tokens in httpOnly cookies (not localStorage)
  • Validate auth server-side (use getUser(), not getSession())
  • Enable RLS on all database tables
  • Set up email verification for new accounts
  • Implement rate limiting on auth endpoints
  • Use strong password requirements
  • Add CSRF protection for form submissions
  • Log authentication events for monitoring
  • Test that protected routes redirect unauthenticated users

Ready to build?

Go from idea to launched product in a week with AI-assisted development.