본문 바로가기
FrontEnd/Next.js

[Next.js] supabase auth를 통해 next 프로젝트에 oauth 접목 시키기 (feat. Multiple GoTrueClient instances detected in the same browser context 에러)

by 위그든씨 2024. 10. 4.

우선 일반적으로 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 초기 셋팅은 따로 작성하지 않을 것임)

https://supabase.com/docs/guides/auth/social-login/auth-kakao?queryGroups=environment&environment=server

 

Login with Kakao | Supabase Docs

Add Kakao OAuth to your Supabase project

supabase.com

위 문서는 공식문서로써 oauth 를 위한 초기 세팅을 도와준다.

https://supabase.com/dashboard/project/{프로젝트 도메인}/auth/providers

 

Authentication | Supabase

Authenticate your users through a suite of providers and login methods

supabase.com

위 링크에는 카카오에서 발급해 준 REST 앱 키와 시크릿 키를 등록할 수 있고 카카오 개발자에 등록할 리다이렉트 url 을 제공해준다.

아래 사진처럼 supabase가 제공한 url을 카카오 디벨로퍼의 Redirect URI에 등록해주면 된다.

supabase dashboard
kakao developer

위에서 언급한 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;