우선 일반적으로 oauth를 구현하는 로직은 아래와 같다.
1. 유저가 로그인 버튼을 클릭
2. 인증을 처리해줄 sns 로그인 페이지로 이동
3. 로그인 성공 시 해당 인가 서버에서는 code를 리다이렉트 url에 searchParams로 담아서 보내줌
4. 리다이렉트 url에서는 받아온 code를 통해 인가 서버에게 액세스 토큰을 받아옴.
5. 받아온 액세스 토큰을 통해 인가서버로부터 유저의 정보를 받아 올 수 있음
supabase auth 동작은 위의 로직에서 3,4,5 를 해결해준다.
따라서 인가 서버에 등록 될 리다이렉트 url은 supabase가 제공해주는 콜백 url을 등록해줄 필요가 있다.
(다음으로 펼쳐질 내용들은 kakao Oauth를 기준으로 작성 되었고, next.js를 위한 supabase 초기 셋팅은 따로 작성하지 않을 것임)
위 문서는 공식문서로써 oauth 를 위한 초기 세팅을 도와준다.
https://supabase.com/dashboard/project/{프로젝트 도메인}/auth/providers
위 링크에는 카카오에서 발급해 준 REST 앱 키와 시크릿 키를 등록할 수 있고 카카오 개발자에 등록할 리다이렉트 url 을 제공해준다.
아래 사진처럼 supabase가 제공한 url을 카카오 디벨로퍼의 Redirect URI에 등록해주면 된다.
위에서 언급한 key와 redirect uri 를 등록해줬다면 플랫폼들에 등록해 줄 것들은 끝낸 셈이다.
( supabase 에서 발급해준 NEXT_PUBLIC_SUPABASE_URL , NEXT_PUBLIC_SUPABASE_ANON_KEY는 공식 문서에 따라 초기 셋팅시에 해줬어야 한다.)
이제 공식 문서에 나온대로 로그인 시 supabase.auth.siginInWithOAuth 함수를 호출해주면 되는데 본인은 supabase 클라이언트 생성을 클라이언트에 맞춰 생성하였더니 route.ts에서 유저의 정보 값을 읽지 못했고 토큰과 유저 정보가 localStorage에 저장 되는 것을 볼 수 있었다. 따라서 server rendering에 맞춰 아래와 같이 코드를 작성함
import { createBrowserClient } from "@supabase/ssr";
import { SupabaseClient } from "@supabase/supabase-js";
declare global {
var supabase: SupabaseClient | undefined;
}
function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
}
export const supabase = globalThis.supabase || createClient();
global을 선언함으로써 추후 생성 될 것을 막음.
위의 supabase를 통해 아래와 같이 signIn handle 함수를 생성
import { supabase } from "@/utils/supabase/supabaseClient";
const signIn = async (provider: "kakao") => {
try {
const { error, data } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${window.location.origin}/auth/callback?next=${encodeURIComponent(window.location.pathname)}`,
},
});
console.log(data);
if (error) throw new Error(`${provider} 로그인 서버 오류`);
} catch (error) {
return error;
}
};
export default signIn;
위에 보이는 options.reDirectTo는 supabase에서 유저 정보까지 받아왔을 때 이동 될 곳을 뜻한다.
// /app/auth/callback.route.ts
import { NextResponse } from "next/server";
// The client you created from the Server-Side Auth instructions
import { createClient } from "@/utils/supabase/server";
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const code = searchParams.get("code");
// if "next" is in param, use it as the redirect URL
const next = searchParams.get("next") ?? "/";
if (code) {
const supabase = createClient();
const { error } = await supabase.auth.exchangeCodeForSession(code);
if (!error) {
const forwardedHost = request.headers.get("x-forwarded-host"); // original origin before load balancer
const isLocalEnv = process.env.NODE_ENV === "development";
if (isLocalEnv) {
// we can be sure that there is no load balancer in between, so no need to watch for X-Forwarded-Host
return NextResponse.redirect(`${origin}${next}`);
} else if (forwardedHost) {
return NextResponse.redirect(`https://${forwardedHost}${next}`);
} else {
return NextResponse.redirect(`${origin}${next}`);
}
}
}
// return the user to an error page with instructions
return NextResponse.redirect(`${origin}/auth/error`);
}
만약 supabase Client를 클라이언트 렌더링 방식으로 작성했다면 code가 없어서 에러페이지로 이동 될 것이다.
위에서 작성한 ssr 방식으로 supabaseClient로 생성했다면 code가 잘 넘어와서 원하는 곳으로 리다이렉트 됨.
또한 토큰 정보는 쿠키에 자동으로 담겨져서 개발자 도구를 확인해보면 이를 쉽게 확인 가능.
** 개발자 도구를 열어보면 Multiple GoTrueClient instances detected in the same browser context 발생했다는 것을 볼 수 있는데 이것은 supabase client를 두번 생성해서 경고문을 띄워준 것이다. 문서에 적힌대로
supabase provider를 작성하는 대신에 SessionContextProvider의 supabaseClient 값에 위에서 생성한 supabaseClient를 넘겨줬더니 경고문이 안뜨게 됨
// SupabaseProvider.tsx
"use client";
import { supabase } from "@/utils/supabase/supabaseClient";
import { SessionContextProvider } from "@supabase/auth-helpers-react";
interface SupabaseProviderProp {
children: React.ReactNode;
}
const SupabaseProvider: React.FC<SupabaseProviderProp> = ({ children }) => {
// const [supabaseClient] = useState(() => createClientComponentClient());
return (
<SessionContextProvider supabaseClient={supabase}>
{children}
</SessionContextProvider>
);
};
export default SupabaseProvider;
'FrontEnd > Next.js' 카테고리의 다른 글
모노레포로 공통 된 tailwind config를 생성하기(turborepo, next.js, tailwind) - 2024.12.11 기준 (0) | 2024.12.11 |
---|---|
Next.js에서 data를 받아오는 방식에 따른 장단점 (2) | 2024.12.09 |
[Next.js] useMediaQuery 모바일/데스크탑 구분 훅 (feat. window is not defined) (0) | 2024.09.23 |
cloudinary API를 통해 이미지 업로드 해보기 (feat. Next.js) (0) | 2024.09.06 |
Credetials callback function handle error (next-auth v5) (0) | 2024.06.17 |