I am currently working on a project using Next.js with Supabase authentication. I have encountered an issue where the user session is not being detected in the middleware after a successful authentication callback. This results in the middleware continuously redirecting to the sign-in page, creating a redirect loop.
Here is 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");
if (code) {
const supabase = createClient();
const result = await supabase.auth.exchangeCodeForSession(code);
if (result.error) {
console.error("Error exchanging code for session:", result.error);
return NextResponse.redirect(requestUrl.origin + '/auth-error');
}
}
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: [
'/((?!_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();
if (
!user &&
!request.nextUrl.pathname.startsWith("/signin") &&
!request.nextUrl.pathname.startsWith("/api")
) {
const url = request.nextUrl.clone();
url.pathname = "/signin";
return NextResponse.redirect(url);
}
return supabaseResponse;
}
The problem:
- The /api/auth/callback route successfully calls exchangeCodeForSession.
- However, in the subsequent middleware, getUser() returns null.
- This leads to the middleware redirecting to the signin page due to the null user.
Logs:
GET /api/auth/callback?code=97738d2a-3b9f-4c2e-a1aa-d9c667478e29 307 in 29ms
user in middleware: null
What I've attempted: Logging the result of exchangeCodeForSession in the callback route shows a successful session creation.
Checked the middleware multiple times, but it consistently shows the user as null.
Questions:
Why is the user session not being detected in the middleware after a successful callback?
Is there a timing issue causing the middleware to run 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 is the best approach to prevent the redirect loop while still securing routes requiring authentication?
Environment:
"@supabase/ssr": "^0.4.0",
"@supabase/supabase-js": "^2.38.3",
"next": "^14.0.0"
Any insights or suggestions would be highly appreciated. Thank you!