SupaLaunch is a SaaS boilerplate built using Supabase and Next.js. It includes authentication, Stripe payments, Postgres database, 20+ TailwindCSS themes, emails, OpenAI API streaming, file storage and more.
Authentication is a crucial aspect of modern web applications. When building with Next.js and Supabase, implementing secure authentication requires careful handling of sessions and protected routes. In this guide, we'll explore how to use Next.js middleware with Supabase authentication to create a robust auth system.
Middleware in Next.js allows you to run code before a request is completed. It's perfect for:
The middleware runs before cached content and route matching, making it ideal for implementing authentication logic.
First, let's install the required dependencies:
npm install @supabase/supabase-js @supabase/ssr
Create a .env.local
file in your project root:
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
We need two types of Supabase clients:
Let's create utility functions for both:
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export function createClient() {
const cookieStore = cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value
},
},
}
)
}
The middleware is responsible for:
Here's how to implement it:
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { createClient } from '@/utils/supabase/middleware'
export async function middleware(request: NextRequest) {
try {
// Create a response object that we can modify
const response = NextResponse.next()
// Create a Supabase client
const supabase = createClient()
// Refresh the session if needed
const { data: { session }, error } = await supabase.auth.getSession()
// Define protected routes
const protectedRoutes = ['/dashboard', '/profile', '/settings']
const isProtectedRoute = protectedRoutes.some(route =>
request.nextUrl.pathname.startsWith(route)
)
// Redirect to login if accessing protected route without session
if (isProtectedRoute && !session) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Redirect to dashboard if accessing auth pages while logged in
const authRoutes = ['/login', '/register']
const isAuthRoute = authRoutes.some(route =>
request.nextUrl.pathname.startsWith(route)
)
if (isAuthRoute && session) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return response
} catch (e) {
// Handle any errors
return NextResponse.next()
}
}
// Configure which routes use this middleware
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!_next/static|_next/image|favicon.ico).*)',
],
}
With middleware in place, you can now create protected server components:
import { createClient } from '@/utils/supabase/server'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const supabase = await createClient()
// Verify user session
const { data: { user }, error } = await supabase.auth.getUser()
if (error || !user) {
redirect('/login')
}
return (
<div>
<h1>Welcome to your dashboard</h1>
<p>Hello, {user.email}</p>
</div>
)
}
When implementing authentication middleware, keep these security best practices in mind:
Always Verify Sessions Server-Side: Never trust client-side session data alone.
Use getUser()
Instead of getSession()
: The getUser()
method makes a request to Supabase's auth server to validate the token, making it more secure than getSession()
.
Handle Token Refresh: Implement proper token refresh logic to maintain user sessions.
Secure Cookie Handling: Use secure, HTTP-only cookies for storing session data.
// Example of secure cookie configuration
cookies().set('my-cookie', 'value', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 3600 // 1 hour
})
To ensure your authentication middleware works correctly, test these scenarios:
Here's a simple test component:
'use client'
import { createClient } from '@/utils/supabase/client'
import { useEffect, useState } from 'react'
export default function TestAuth() {
const [isLoading, setIsLoading] = useState(true)
const [user, setUser] = useState(null)
const supabase = createClient()
useEffect(() => {
const checkUser = async () => {
const { data: { user } } = await supabase.auth.getUser()
setUser(user)
setIsLoading(false)
}
checkUser()
}, [])
if (isLoading) return <div>Loading...</div>
return (
<div>
<h1>Auth Test</h1>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
)
}
Implementing authentication middleware with Next.js and Supabase provides a robust solution for protecting your application's routes. The middleware pattern allows for centralized auth logic, making it easier to maintain and update your authentication system.
Remember to:
By following these practices, you'll have a secure and reliable authentication system for your Next.js application.