본문 바로가기

공부/Next

[Next, Supabase Auth] 이메일, 소셜 로그인 기능 구현 및 트리거 설정

반응형

 

Supabase Auth 선택 이유 ✨

Supabase는 PostgreSQL을 사용해 데이터 정규화, 복잡한 쿼리 작업 등 SQL의 모든 기능을 사용할 수 있습니다. 그리고 요즘 떠오르는 기술이며 공식 문서 또한 너무 잘 되어 있기 때문에 적용하기 쉽다고 판단해서 선택하게 되었습니다.

 

사용 방법 📖

Authentication > Providers

Providers

Providers 탭으로 이동하면 다양한 로그인을 선택할 수 있습니다.

이메일을 이용해 간단한 로그인 구현과 OAuth를 이용한 소셜 로그인도 구현할 수 있습니다.

 

 

✨ Provider를 등록하는 방법은 공식 문서에 너무 잘 나와 있으니 생략하도록 하겠습니다.

 

Google

 

Login with Google | Supabase Docs

Use Sign in with Google on the web, in native apps or with Chrome extensions

supabase.com

Kakao

 

Login with Kakao | Supabase Docs

Add Kakao OAuth to your Supabase project

supabase.com

 

Authentication

Authentication > Users

Authentication > Users

 

사용자가 회원가입(이메일, SNS)을 하게 되면 `Users`라는 테이블에 해당 유저에 정보가 저장됩니다.

 

profiles 테이블 추가하기

Table Editor를 통해 `profiles`테이블을 생성해 주겠습니다.

profiles

 

속성으로 `id, user_name, email, avatar_url` 컬럼을 추가하고 `id`컬럼 타입은 uuid로 설정하겠습니다.

 

이후 auth.users 테이블 `uid`를 가져와 public.profiles의 `id`와 연결을 해줍니다.

 

Action은 해당 행이 지워지면 관계된 행도 같이 삭제할 수 있도록 Cascade로 선택하고 `Save`버튼을 눌러 저장합니다.

 

 

함수와 트리거 설정 📖

 유저가 회원가입을 하고 `auth.users` 테이블에 새로운 행이 추가될 때 자동으로 `profiles`테이블에 해당 유저 정보를 추가하기 위해 설정합니다.

 

add_new_user() 함수 추가

`SQL Editor`에서 해당 함수를 작성해 줍니다.

create function public.add_new_user()
returns trigger as $$
begin
if new.raw_app_meta_data ->> 'provider' = 'email' then
insert into
  public.profiles (id, email, user_name, avarta_url)
values
  (
    new.id,
    new.email,
    new.raw_user_meta_data ->> 'user_name',
    new.raw_user_meta_data ->> 'avarta_url'
  );

elsif new.raw_app_meta_data ->> 'provider' = 'kakao' then
insert into
  public.profiles (id, email, user_name, avatar_url)
values
  (
    new.id,
    new.email,
    new.raw_user_meta_data ->> 'name',
    new.raw_user_meta_data ->> 'avatar_url'
  );

elsif new.raw_app_meta_data ->> 'provider' = 'google' then
insert into
  public.profiles (id, email, user_name, avatar_url)
values
  (
    new.id,
    new.email,
    new.raw_user_meta_data ->> 'name',
    new.raw_user_meta_data ->> 'avatar_url'
  );
  
end if;

return new;

end;

$$ language plpgsql security definer;

 

이메일과 SNS로그인을 같이 사용했기 때문에 if문을 추가해서 provider별로 테이블의 데이터를 추가하는 sql을 다르게 설정해 주었습니다.

 

on_auth_user_created 트리거 추가

`auth.users` 테이블에 새로운 행이 추가되면 add_new_user() 함수를 실행해 `profiles` 테이블에 해당 유저 정보를 자동으로 추가해 줍니다.

create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.add_new_user();

 

 

함수는 database탭에서 functions > schema:public에서 조회가 가능합니다.

Database > Functions > schema:public

 

트리거는 database탭에서 Triggers > schema:auth에서 조회가 가능합니다.

Database > Triggers > schema:auth

 

결과

이후 유저가 회원가입을 하게 되면 `profiles`테이블에 유저 정보가 잘 저장되는 걸 확인하실 수 있습니다.

 

로그인, 회원가입 하기 📖

기본 구성

Next.js에서 Supabase를 사용하기 위한 기본적인 설정 파일입니다.

 

utils > supabase > client.ts

import { createBrowserClient } from '@supabase/ssr';

export const createClient = () =>
  createBrowserClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!);

const browserClient = createClient();

export default browserClient;

 

utils > supabase > server.ts

import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';

export const createClient = () => {
  const cookieStore = cookies();

  return createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, {
    cookies: {
      getAll() {
        return cookieStore.getAll();
      },
      setAll(cookiesToSet) {
        try {
          cookiesToSet.forEach(({ name, value, options }) => {
            cookieStore.set(name, value, options);
          });
        } catch (error) {}
      }
    }
  });
};

 

utils > supabase > middleware.ts

import { createServerClient } from '@supabase/ssr';
import { type NextRequest, NextResponse } from 'next/server';

export const updateSession = async (request: NextRequest) => {
  try {
    let response = NextResponse.next({
      request: {
        headers: request.headers
      }
    });

    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 }) => request.cookies.set(name, value));
            response = NextResponse.next({
              request
            });
            cookiesToSet.forEach(({ name, value, options }) => response.cookies.set(name, value, options));
          }
        }
      }
    );

    return response;
  } catch (e) {
    return NextResponse.next({
      request: {
        headers: request.headers
      }
    });
  }
};


이메일 회원가입

회원가입

const serverClient = createClient();

const {
  data: { user },
  error
} = await serverClient.auth.signUp({
  email: userInfo.email, 
  password: userInfo.password,
  options: {
    data: {
      user_name: userInfo.nickname
    }
  }
});

 

이메일로 회원가입을 진행할 때 email, password 외에 저장한 데이터는 options > data 객체에 넣어 요청을 보내면 `auth.users`테이블의 `raw_user_meta_data`에 저장됩니다.

raw_user_meta_data

 

 

SNS 회원가입

구글, 카카오 로그인 버튼을 누르게 되면 회원가입을 진행하게 됩니다. 

구글, 카카오

 


`signInWithOAuth`함수를 부르게 되면 `redirectTo: window.origin + '/auth/callback'`으로 리다이렉션을 해서 origin으로 리다이렉션 될 수 있게 잡아주는 역할을 합니다. 

 

이렇게 설정해주지 않으면 서버, 브라우저 클라이언트 간 연결이 매끄럽게 되지 않을 수 있습니다.

const signInWithSocial = async () => {
    await browserClient.auth.signInWithOAuth({
      provider: 'google or kakao',
      options: {
        redirectTo: window.origin + '/auth/callback'
      }
    });
  };

 

 

app > auth > callback > route.ts에 라우터 핸들러를 구성해줍니다.

import { NextResponse } from 'next/server';
import { createClient } from '@/utils/supabase/server';

export const GET = async (request: Request) => {
  const { searchParams, origin } = new URL(request.url);
  const code = searchParams.get('code');
  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');
      const isLocalEnv = process.env.NODE_ENV === 'development';
      if (isLocalEnv) {
        return NextResponse.redirect(`${origin}${next}`);
      } else if (forwardedHost) {
        return NextResponse.redirect(`https://${forwardedHost}${next}`);
      } else {
        return NextResponse.redirect(`${origin}${next}`);
      }
    }
  }

  return NextResponse.redirect(`${origin}/auth/auth-code-error`);
};


이후
회원가입 및 로그인을 성공하게 되면 해당 유저 정보를 반환하게 되고 `auth.users`테이블의 `raw_user_meta_data`에 저장됩니다.

{
  user: {
  id: '',
  aud: 'authenticated',
  role: 'authenticated',
  email: '',
  email_confirmed_at: '2024-10-14T01:37:12.623212Z',
  phone: '',
  confirmed_at: '2024-10-14T01:37:12.623212Z',
  last_sign_in_at: '2024-10-14T01:37:14.577923Z',
  app_metadata: { provider: 'google OR kakao', providers: [ 'google OR kakao' ] },
  user_metadata: {
  avatar_url: '',
  email: '',
  email_verified: true,
  full_name: '',
  iss: 'https://kapi.kakao.com',
  name: '',
  phone_verified: false,
  preferred_username: '',
  provider_id: '',
  sub: '',
  user_name: ''
},

 

 

이렇게 이메일 로그인과 소셜 로그인을 구현하실 수 있습니다!

이슈❗

신규 사용자 추가 시 오류

 

[트러블 슈팅] supabase에서 Google, Kakao 로그인 시 신규 사용자 저장 오류

소셜 로그인을 구현하는 과정에서 발생한 트러블 슈팅에 대해 정리해 보려고 합니다.http://auth/auth-code-error#error=server_error&error_code=500&error_description=Database+error+saving+new+user 발생한 오류 🔥Google과

mingos-habitat.tistory.com

 


출처 🏷️

https://ramincoding.tistory.com/entry/React-Supabase-%EC%9D%B4%EB%A9%94%EC%9D%BC-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%B0%8F-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%ED%8A%B8%EB%A6%AC%EA%B1%B0-%EC%84%A4%EC%A0%95

 

반응형