S
Supabase2mo ago
Saif

Supabase + Next.js: Sign Up and Magic Link Flow. Am I Doing This Right?

I’m using Supabase for authentication in my Next.js app and I want to confirm if I’m handling sign up confirmations + magic links correctly. When I call signInWithOtp, I set:
emailRedirectTo: ${window.location.origin}/api/auth/callback
emailRedirectTo: ${window.location.origin}/api/auth/callback
On /api/auth/callback, I extract search params (code, access_token, refresh_token) and set the session manually:
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const code = searchParams.get('code');
const access_token = searchParams.get('access_token');
const refresh_token = searchParams.get('refresh_token');

if (code) return loginWithCode(code);
if (access_token && refresh_token) return loginWithTokens(access_token, refresh_token);

return NextResponse.redirect(new URL('/auth/signin', request.url));
}

async function loginWithCode(code: string) {
const supabase = await createClient();
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
if (error || !data.session) return new Response('Error exchanging code', { status: 400 });
return createRedirectResponse(data.session);
}

async function loginWithTokens(access_token: string, refresh_token: string) {
const supabase = await createClient();
const { data, error } = await supabase.auth.setSession({ access_token, refresh_token });
if (error || !data.session) return new Response('Error setting session', { status: 400 });
return createRedirectResponse(data.session);
}
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const code = searchParams.get('code');
const access_token = searchParams.get('access_token');
const refresh_token = searchParams.get('refresh_token');

if (code) return loginWithCode(code);
if (access_token && refresh_token) return loginWithTokens(access_token, refresh_token);

return NextResponse.redirect(new URL('/auth/signin', request.url));
}

async function loginWithCode(code: string) {
const supabase = await createClient();
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
if (error || !data.session) return new Response('Error exchanging code', { status: 400 });
return createRedirectResponse(data.session);
}

async function loginWithTokens(access_token: string, refresh_token: string) {
const supabase = await createClient();
const { data, error } = await supabase.auth.setSession({ access_token, refresh_token });
if (error || !data.session) return new Response('Error setting session', { status: 400 });
return createRedirectResponse(data.session);
}
2 Replies
Saif
SaifOP2mo ago
Another problem is that sometimes Supabase puts tokens in the URL hash (#) instead of query params, which cannot be accessed on the server. To cover that, I added client-side handling on the signin page:
useEffect(() => {
const hash = window.location.hash;
if (!hash) return;

const searchParams = new URLSearchParams(hash.replace('#', '?'));
const code = searchParams.get('code');
const access_token = searchParams.get('access_token');
const refresh_token = searchParams.get('refresh_token');

const supabase = createClient();
if (code) supabase.auth.exchangeCodeForSession(code);
if (access_token && refresh_token) supabase.auth.setSession({ access_token, refresh_token });

redirect('/dashboard');
}, []);
useEffect(() => {
const hash = window.location.hash;
if (!hash) return;

const searchParams = new URLSearchParams(hash.replace('#', '?'));
const code = searchParams.get('code');
const access_token = searchParams.get('access_token');
const refresh_token = searchParams.get('refresh_token');

const supabase = createClient();
if (code) supabase.auth.exchangeCodeForSession(code);
if (access_token && refresh_token) supabase.auth.setSession({ access_token, refresh_token });

redirect('/dashboard');
}, []);
Am I over complicating his by handling both cases manually, or is there another way to handle this?

Did you find this page helpful?