Next.js+Supabase认证后中间件无法检测会话致重定向循环
Next.js + Supabase认证回调后中间件无法检测会话导致重定向循环问题
我在开发基于Next.js和Supabase认证的项目时,遇到了认证回调成功后中间件无法检测用户会话的问题,导致中间件持续重定向到登录页,形成重定向循环。
问题现象
/api/auth/callback路由成功调用exchangeCodeForSession,会话创建成功- 后续中间件中
getUser()返回null - 因检测不到用户,中间件重定向到登录页,形成循环
日志信息:
GET /api/auth/callback?code=97738d2a-3b9f-4c2e-a1aa-d9c667478e29 307 in 29ms user in middleware: null
相关代码
/api/auth/callback/route.ts
import { NextResponse, NextRequest } from "next/server"; import { createClient } from "@/utils/supabase/server"; import config from "@/config"; export const dynamic = "force-dynamic"; // This route is called after a successful login. It exchanges the code for a session and redirects to the callback URL (see config.js). export async function GET(req: NextRequest) { const requestUrl = new URL(req.url); const code = requestUrl.searchParams.get("code"); const next = requestUrl.searchParams.get("next"); console.log("next in auth callback:", next); if (code) { const supabase = createClient(); const result = await supabase.auth.exchangeCodeForSession(code); console.log("exchangeCodeForSession result:", JSON.stringify(result, null, 2)); if (result.error) { console.error("Error exchanging code for session:", result.error); return NextResponse.redirect(requestUrl.origin + '/auth-error'); } // You can access other properties like result.data here if needed } // URL to redirect to after sign in process completes return NextResponse.redirect(requestUrl.origin); }
middleware.ts
/* middleware.ts */ import { type NextRequest } from 'next/server'; import { updateSession } from '@/utils/supabase/middleware'; export async function middleware(request: NextRequest) { return await updateSession(request); } export const config = { matcher: [ /* * Match all request paths except: * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) * - images - .svg, .png, .jpg, .jpeg, .gif, .webp * Feel free to modify this pattern to include more paths. */ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)' ] };
utils/supabase/middleware.ts
import { createServerClient } from "@supabase/ssr"; import { NextResponse, type NextRequest } from "next/server"; export async function updateSession(request: NextRequest) { let supabaseResponse = 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) ); supabaseResponse = NextResponse.next({ request, }); cookiesToSet.forEach(({ name, value, options }) => supabaseResponse.cookies.set(name, value, options) ); }, }, } ); const { data: { user }, } = await supabase.auth.getUser(); //console data console.log("user in middleware:",user) if ( !user && !request.nextUrl.pathname.startsWith("/signin") && !request.nextUrl.pathname.startsWith("/api") ) { // no user, potentially respond by redirecting the user to the login page const url = request.nextUrl.clone(); url.pathname = "/signin"; return NextResponse.redirect(url); } return supabaseResponse; }
已尝试操作
- 在回调路由中打印
exchangeCodeForSession结果,确认会话创建成功 - 多次检查中间件逻辑,用户始终返回
null
疑问
- 为何认证回调成功后中间件无法检测到用户会话?
- 是否存在中间件在会话完全建立前执行的时序问题?
- 如何确保回调后会话能在中间件中被正确设置并检测到?
- 如何在保护需认证路由的同时避免重定向循环?
环境版本
"@supabase/ssr": "^0.4.0", "@supabase/supabase-js": "^2.38.3", "next": "^14.0.0",
解决方案
1. 修复回调路由的Cookie传递逻辑
当前回调路由未将Supabase生成的会话Cookie附加到重定向响应,导致浏览器无法保存会话。修改/api/auth/callback/route.ts:
export async function GET(req: NextRequest) { const requestUrl = new URL(req.url); const code = requestUrl.searchParams.get("code"); const next = requestUrl.searchParams.get("next") || "/"; if (code) { const supabase = createClient(); const { error } = await supabase.auth.exchangeCodeForSession(code); if (error) { console.error("Error exchanging code for session:", error); return NextResponse.redirect(`${requestUrl.origin}/auth-error`); } } // 将Supabase生成的会话Cookie附加到重定向响应 const response = NextResponse.redirect(`${requestUrl.origin}${next}`); const cookies = await supabase.cookies.getAll(); cookies.forEach(({ name, value, options }) => { response.cookies.set(name, value, options); }); return response; }
2. 修正中间件的Cookie处理逻辑
中间件中修改只读的request.cookies是无效的,需只操作响应Cookie,同时先刷新会话再获取用户:
export async function updateSession(request: NextRequest) { let supabaseResponse = 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) { // 仅更新响应的Cookie,不再修改request supabaseResponse = NextResponse.next({ request }); cookiesToSet.forEach(({ name, value, options }) => { supabaseResponse.cookies.set(name, value, options); }); }, }, } ); // 先刷新会话,确保最新状态 await supabase.auth.getSession(); const { data: { user } } = await supabase.auth.getUser(); console.log("user in middleware:", user); // 排除回调路由,避免拦截认证流程 if ( !user && !request.nextUrl.pathname.startsWith("/signin") && !request.nextUrl.pathname.startsWith("/api/auth/callback") && !request.nextUrl.pathname.startsWith("/api") ) { const url = request.nextUrl.clone(); url.pathname = "/signin"; url.searchParams.set("next", request.nextUrl.pathname); return NextResponse.redirect(url); } return supabaseResponse; }
3. 更新中间件匹配规则
在middleware.ts的匹配规则中排除回调路由,避免中间件拦截认证回调请求:
export const config = { matcher: [ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$|api/auth/callback).*)' ] };
关键原因说明
- 回调路由未将Supabase生成的会话Cookie附加到响应,导致浏览器无会话信息
- 中间件错误修改只读的
request.cookies,导致会话Cookie未正确传递给Supabase客户端 - 中间件匹配规则未排除回调路由,导致认证流程被中断重定向
内容的提问来源于stack exchange,提问作者sunpy
相关产品推荐
相关产品推荐

