I'm working on a Next.js project with Supabase authentication, and I'm encountering an issue where the user session is not being detected in the middleware after a successful authentication callback. This causes the middleware to continuously redirect to the signin page, creating a redirect loop.
Here's the relevant code:
/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);
}
middelware.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;
}
The issue:
Logs:
GET /api/auth/callback?code=97738d2a-3b9f-4c2e-a1aa-d9c667478e29 307 in 29ms
user in middleware: null
What I've tried: Logging the result of exchangeCodeForSession in the callback route, which shows a successful session creation.
Checking the middleware multiple times, but it consistently shows the user as null.
Questions:
Why isn't the user session being detected in the middleware after a successful callback?
Is there a timing issue where the middleware is executing before the session is fully established?
How can I ensure that the session is properly set and detectable in the middleware after the callback?
What's the best way to prevent the redirect loop while still protecting routes that require authentication?
Environment:
"@supabase/ssr": "^0.4.0",
"@supabase/supabase-js": "^2.38.3",
"next": "^14.0.0",
Any insights or suggestions would be greatly appreciated. Thank you!
I set the Site URL to http://localhost:3000 instead of http://127.0.0.1:3000, the problem solved