SupaLaunch logo
SUPALAUNCH
ShowcaseDocs
SupaLaunch
HomeDemoDocumentationBlogShowcaseServices and FrameworksTerms of ServicePrivacy policy
Tools
DB Schema GeneratorStartup Idea Generator
Other Projects
Syntha AICreateChatsCron Expression GeneratorAI Prompts Generator
Contacts
Created with ❤️ by Denisdenis@supalaunch.com
  • Showcase
  • Docs
Blog

How to Implement Authentication Middleware with Next.js and Supabase

2 Dec 2024
By Denis
nextjssupabaseauthenticationmiddleware

Launch your startup with our Supabase + NextJS Starter Kit

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.

  • Save weeks of your time: No need to setup authentication, payments, emails, file storage, etc.
  • Focus on what matters: Spend your time building your product, not boilerplate code.
Get started with SupaLaunch

Implementing Authentication Middleware with Next.js and Supabase

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.

What is Next.js Middleware?

Middleware in Next.js allows you to run code before a request is completed. It's perfect for:

  • Authentication and session management
  • Protecting routes from unauthorized access
  • Redirecting users based on their auth status
  • Modifying request/response headers
  • Rewriting URLs

The middleware runs before cached content and route matching, making it ideal for implementing authentication logic.

Setting Up the Project

First, let's install the required dependencies:

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

Environment Configuration

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

Creating Supabase Clients

We need two types of Supabase clients:

  1. A client for browser-side components
  2. A server-side client for middleware and server components

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
        },
      },
    }
  )
}

Implementing Authentication Middleware

The middleware is responsible for:

  • Refreshing authentication tokens
  • Managing session cookies
  • Protecting routes from unauthorized access

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).*)',
  ],
}

Protecting Server Components

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>
  )
}

Important Security Considerations

When implementing authentication middleware, keep these security best practices in mind:

  1. Always Verify Sessions Server-Side: Never trust client-side session data alone.

  2. 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().

  3. Handle Token Refresh: Implement proper token refresh logic to maintain user sessions.

  4. 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
})

Testing the Authentication Flow

To ensure your authentication middleware works correctly, test these scenarios:

  1. Accessing protected routes without authentication
  2. Accessing auth pages while logged in
  3. Session persistence across page reloads
  4. Token refresh behavior
  5. Logout functionality

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>
  )
}

Conclusion

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:

  • Always validate sessions server-side
  • Implement proper error handling
  • Use secure cookies
  • Test thoroughly across different scenarios

By following these practices, you'll have a secure and reliable authentication system for your Next.js application.

Additional Resources

  • Next.js Middleware Documentation
  • Supabase Auth Documentation
  • Next.js Server Actions