Next.js Discord

Discord Forum

Next.js Middleware with Supabase SSR – Security Review & Optimizing "getUser()" Calls

Unanswered
! ichi posted this in #help-forum
Open in Discord
Hi everyone,

I’m using Supabase’s SSR client inside a Next.js middleware to guard my routes and manage sessions. Here’s the current implementation in supabase/middleware.ts:

My questions:

1. Security Review: Are there any security vulnerabilities or misconfigurations in this approach?
2. Optimizing getUser(): Since supabase.auth.getUser() runs on every request that passes through this middleware, what patterns or caching strategies would you recommend to reduce repeated token validations while still ensuring user sessions are secure and up-to-date?

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

export async function updateSession(request: NextRequest) {
  // 1. Create the initial NextResponse once; do not re-create it later.
  const supabaseResponse = NextResponse.next({ request })

  // 2. Initialize Supabase SSR client, handling cookies only on the response.
  const supabase = createServerClient(
    process.env.SUPABASE_URL!,
    process.env.SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) => {
            supabaseResponse.cookies.set(name, value, {
              ...options,
              httpOnly: true,
              secure: true,
              sameSite: 'lax',
            })
          })
        },
      },
    }
  )

  // IMPORTANT: Always call getUser() here to re-validate the token.
  const {
    data: { user },
  } = await supabase.auth.getUser()

  const { pathname } = request.nextUrl

  // Route "/" → /private or /public
  if (pathname === '/') {
    const targetUrl = request.nextUrl.clone()
    targetUrl.pathname = user ? '/private' : '/public'
    const redirectResponse = NextResponse.redirect(targetUrl)
    supabaseResponse.cookies.getAll().forEach(cookie => {
      redirectResponse.cookies.set(cookie.name, cookie.value, {
        httpOnly: cookie.httpOnly,
        secure: cookie.secure,
        path: cookie.path,
        domain: cookie.domain,
        sameSite: cookie.sameSite,
        maxAge: cookie.maxAge,
        priority: cookie.priority,
      })
    })
    return redirectResponse
  }

  // Allow all /auth routes
  if (pathname.startsWith('/auth')) {
    return supabaseResponse
  }

  // Block unauthenticated users on /private
  if (pathname.startsWith('/private') && !user) {
    const loginUrl = request.nextUrl.clone()
    loginUrl.pathname = '/auth'
    const redirectResponse = NextResponse.redirect(loginUrl)
    supabaseResponse.cookies.getAll().forEach(cookie => {
      redirectResponse.cookies.set(cookie.name, cookie.value, {
        httpOnly: cookie.httpOnly,
        secure: cookie.secure,
        path: cookie.path,
        domain: cookie.domain,
        sameSite: cookie.sameSite,
        maxAge: cookie.maxAge,
        priority: cookie.priority,
      })
    })
    return redirectResponse
  }

  // Block authenticated users on /public
  if (pathname.startsWith('/public') && user) {
    const privateUrl = request.nextUrl.clone()
    privateUrl.pathname = '/private'
    const redirectResponse = NextResponse.redirect(privateUrl)
    supabaseResponse.cookies.getAll().forEach(cookie => {
      redirectResponse.cookies.set(cookie.name, cookie.value, {
        httpOnly: cookie.httpOnly,
        secure: cookie.secure,
        path: cookie.path,
        domain: cookie.domain,
        sameSite: cookie.sameSite,
        maxAge: cookie.maxAge,
        priority: cookie.priority,
      })
    })
    return redirectResponse
  }

  // Finally, return the (possibly updated) response
  return supabaseResponse
}


Thanks in advance for your feedback!

0 Replies