Next.js Middleware with Supabase SSR – Security Review & Optimizing "getUser()" Calls
Unanswered
! ichi posted this in #help-forum
! ichiOP
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
My questions:
1. Security Review: Are there any security vulnerabilities or misconfigurations in this approach?
2. Optimizing
Thanks in advance for your feedback!
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!