You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

疑问

  1. 为何认证回调成功后中间件无法检测到用户会话?
  2. 是否存在中间件在会话完全建立前执行的时序问题?
  3. 如何确保回调后会话能在中间件中被正确设置并检测到?
  4. 如何在保护需认证路由的同时避免重定向循环?

环境版本

"@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:17:04