2025 . 오은

repo

Supabase auth with Next.js-3

Step 7. 프로바이더 로그인 버튼 예시

"use client";

import useAuth from "@/hooks/useAuth";
import { showAlert } from "@/lib/openCustomAlert";
import { usePathname } from "next/navigation";
import { FcGoogle } from "react-icons/fc";

function GoogleLogInButton() {
	const { loginWithProvider } = useAuth();
	const pathname = usePathname();
	
	const handleClickGoogle = async () => {
		if (pathname === "/recover") {
			return showAlert("caution", "비밀번호 복구 페이지에서는 소셜로그인이 불가합니다");
		}
		loginWithProvider("google");
	};

	return 
	<FcGoogle className="w-11 h-11 cursor-pointer" onClick={handleClickGoogle} />;
}

export default GoogleLogInButton;

대략 위처럼 만들면 됩니다. 그러면 loginWithProvider 를 봐야겠죠?

Step 8. loginWithProvider 함수

  
const loginWithProvider = async (provider: string) => {
	try {
		setIsPending(true);
		const response = await fetch(
		`${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/provider?provider=${provider}`
		);
		
		if (!response.ok) {
			throw new Error("fetch 실패");
		}
		const data = await response.json();
		
		queryClient.invalidateQueries({ queryKey: ["user"] });
		router.replace(data.url);
	
		showAlert("success", "로그인 성공");
	} catch (error) {
		console.error(error);
	}
};

대략 위처럼 생겼습니다. /api/auth/provider 를 향해서 get 을 날리고 파라미터로 프로바이더 종류를 넘깁니다.

Step 9. route handler(provider)

import { createClient } from "@/supabase/server";
import { Provider } from "@supabase/supabase-js";
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
	const { searchParams } = new URL(request.url);
	const provider = searchParams.get("provider");
	
	const supabase = createClient();
	const { data, error } = await supabase.auth.signInWithOAuth({
		provider: provider as Provider,
		options: {
			redirectTo: `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/callback`,
		},
	});

	if (error) {
		return NextResponse.json({ error: error?.message }, { status: 401 });
	}

	return NextResponse.json(data, { status: 200 });
}

역시 대략 위처럼 생겼습니다. 포인트는 /api/auth/callback 여기로 리다이렉트 시킨다는 점입니다.

Step 10. route handler(callback)

import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET(request: Request) {

	const { searchParams, origin } = new URL(request.url);
	const code = searchParams.get("code");
	const next = searchParams.get("next") ?? "/";

  
	if (code) {
		const cookieStore = cookies();
		const supabase = createServerClient(
			process.env.NEXT_PUBLIC_SUPABASE_URL!,
			process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
				{
					cookies: {
						get(name: string) {
						return cookieStore.get(name)?.value;
					},
					set(name: string, value: string, options: CookieOptions) {
						cookieStore.set({ name, value, ...options });
					},
					remove(name: string, options: CookieOptions) {
						cookieStore.delete({ name, ...options });
					},
				},
			}
		);

		const { error } = await supabase.auth.exchangeCodeForSession(code);
		
		if (!error) {
			return NextResponse.redirect(`${origin}${next}`);
		}
	}
	// 여기도 auth-code-error 이런 페이지가 있어야 리다이렉트 되니 미리 만들어놓아야함 
	// 싫으면 다른데로 보내면 됨
	return NextResponse.redirect(`${origin}/auth/auth-code-error`);
}

// 만일 소셜 로그인인데, 이메일이 같으면(깃헙로그인이든 구글이든) 
// 수파베이스 auth 에는 그냥 업데이트만 됨

callback 은 위처럼 생겼습니다. 코드 내용물은 supabase 가이드와 똑같으니 참고 하면 될 것 같습니다. 여기참고

그런데 희한한 점은 소셜 로그인인데 이메일이 같을 경우... 예를들어 깃헙에 사용된 이메일이 gmail 이고 이것이 google 과 같은 아디라면, 수파베이스 auth 에는 그냥 업데이트만 됩니다?