Supabase에서 OAuth적용해서 로그인 하기

박경현·2024년 10월 21일

이번에 실시간 채팅 어플을 만들기 위해서 로그인 기능을 만들다가 OAuth를 자연스럽게 사용해보게 되어서 관련 내용을 정리합니다.

항상 UI/UX 측면에서 정말 필요하다 생각한 기능인데 이번에 제대로 사용해보고 더 깊이 있게 사용법도 알게 되었습니다..!!

일단 OAuth가 뭘까?

OAuth는 그냥 SNS를 이용한 로그인으로 편리한 기능이다! 로만 알고 있었지 제대로 모르게 있었습니다.

Open Authorization으로 제 3자를 통해(Google, Naver, Kakao 등) 인증받을 수 있는 인증 프로토콜입니다.
사용자의 자격증명을 외부 어플리케이션에 직접 제공하지 않고도 특정 리소스에 접근가능합니다.

OAuth의 핵심 개념

  1. 리소스 소유자(Resource Owner): 서비스에 접근하려는 사용자 - Google계정으로 로그인하려는 유저
  2. Client - 리소스 소유자를 대신하여 권한을 얻고 리소스에 접근하는 어플리케이션 - Google계정으로 로그인하려는 웹사이트
  3. 인증서버(Authorization Server): 클라이언트가 리소스 소유자로부터 접근권한을 얻기 위해 권한을 부여 - Google 서버
  4. 리소스 서버: 보호된 리소스를 호스팅하는 서버, 권한이 부여된 클라이언트만 접근 가능 - Google Drive API 서버

OAuth의 주요 흐름

  1. 사용자 인증요청: 클라이언트는 리소스소유자에게 인증서버를 통해 로그인하도록 요청합니다.
  2. 사용자 인증: 사용자는 클라이언트가 접근 권한을 얻을 수 있도록 인증서버에 로그인합니다.
  3. 승인 및 토큰 발급: 사용자가 클라이언트에 특정 정보나 리소스에 접근할 수 있도록 허용하면 인증서버는 Access token을 발급해줍니다.

    이 토큰은 리소스 서버에 접근할 수 있는 권한을 나타냅니다.

  4. 리소스 접근: 클라이언트는 액세스 토큰을 사용하여 리소스 서버에 접근하고 필요한 데이터를 요청할 수 있습니다.

OAuth의 장점

보안 강화: 비밀번호와 같은 자격 증명을 외부 서비스에 제공하지 않아도 됩니다.
권한 제어: 사용자는 특정 데이터만 클라이언트에게 접근을 허용할 수 있으며, 필요시 언제든지 권한을 철회할 수 있습니다.
범용성: 여러 서비스(Google, Facebook, GitHub 등)에서 하나의 계정으로 다양한 웹사이트나 앱에 로그인할 수 있습니다.

Supabase와 Google 로그인

그렇다면 이제 실제로 Google을 이용한 로그인방식과
기본 이메일, 패스워드 방식을 이용한 회원가입 및 로그인을 설계합니다.

차고로 기능 공부에 몰빵(?)인 상태라 아에 CSS는 생각하지 않았습니다....

이때 Supabase에 Google OAuth ID와 Password를 적어서 설정했습니다.

React-query를 사용해서 세션을 관리하자

매번 recoil을 사용하다가 이번에 React-query를 적용하고 싶어 함께 사용해보았습니다.

app/hooks/useSession.ts

하지만 layout.tsx까지 전부 CSR이 되어버려서 조금 더 공부하고 정확하게 사용해야할거 같습니다...

import { supabase } from '@/supabaseClient';
import { useQuery } from '@tanstack/react-query';

const fetchSession = async() => {
	const {data} = await supabase.auth.getSession();
    return data.session;
}

const useSession = () => {
	return useQuery({
    	queryKey: ['session'],
        queryFn: fetchSession,
        staleTime: 60000, // 1분간 세션 캐싱,
        refetchOnWindowFocus: true, // 창이 포커스될 때 세션 재검증
    });
};

export default useSession;

회원가입 혹은 로그인이 성공시 오는 기본 page

회원가입페이지, 로그인 페이지로 이동이 가능한 기본 페이지입니다.
로그인을 성공하면 아래 로그아웃버튼을 활성화 시킵니다.

app/page.tsx

"use client"

import { useEffect } from "react";
import { supabase } from "@/supabaseClient";
// 생성한 세션 훅
import Link from "next/link";
import useSession from "./hooks/useSession";

export default function Home() {
	const {data: session, refetch} = useSession();
    
    useEffect(()=>{
    	const {data: {subscription} } = supabase.auth.onAuthStateChange(() => {
        	refetch();
        });
        return () => {
        	subscription?.unsubscribe();
        };
    }, [refetch]);
    
    const signOut = async () => {
    	await supabase.auth.signOut();
        refetch(); // 로그아웃 후 세션 재검증
    };
    
    return (
    	<div>
        	<h1>OAuth 로그인 및 이메일회원가입 연습</h1>
            <br/>
            <Link href={"/auth/signup"}>회원가입 페이지</Link>
            <br />
            <br />
            <Link href={"/auth/login"}>로그인 페이지</Link>
            <br />
            <br />
            {
            	session ? (
                	<button onClick={signOut}>로그아웃</button>
                ) 
                : (<p>로그인 안되어 있습니다.</p>)
            }
        </div>
    );
}

이메일과 Password를 활용한 회원가입

Google로그인은 회원가입이 필요없기에 따로 회원가입페이지가 없습니다.
하지만 일반 이메일과 비밀번호로 로그인하려는 유저도 있을 수 있기 때문에 기본방식의 회원가입 및 로그인도 같이 구현했습니다.

app/auth/signup/page.tsx

"use client";

import { supabase } from '@/supabaseClient';
import { useRouter } from 'next/navigation';
import { useState } from 'react';

export default function SignUp() {
	const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [error, setError] = useState(null);
    const router = useRouter();
    
    const signUpWithEmail = async (e: React.FormEvent) => {
    	e.preventDefautl();
        const {data, error} = await supabase.auth.signUp({
        	email,
            password,
        });
        
        if (error) {
        	setError(error.message);
        } else {
        	router.push('/auth/login');
        }
        
    };
    
    return (
    <div className="m-4">
      <h2 className="mb-4 text-2xl font-bold">회원가입</h2>
      <form onSubmit={signUpWithEmail}>
        <div>
          <label htmlFor="email">Email:</label>
          <input
            type="email"
            id="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
            className="p-2 border border-2"
          />
        </div>
        <div>
          <label htmlFor="password">Password:</label>
          <input
            type="password"
            id="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            required
            className="p-2 border border-2"
          />
        </div>
        <button
          type="submit"
          className='flex items-center gap-2 m-4 border border-2'>
          <span className='p-2 font-bold text-white bg-blue-500 border'>회원가입</span>
        </button>

        <button
              type="button"
              onClick={() => router.push('/auth/login')}
              className='flex items-center gap-2 m-4 border border-2'>
              <span className='p-2 font-bold text-white bg-blue-500 border'>로그인</span>
            </button>
      </form>
      {error && <p className="text-red-500">{error}</p>}
    </div>
  );
}

기본 로그인과 Google Login

이제 오늘의 핵심인 OAuth와 그리고 기본 로그인 방식의 함수를 정의한 부분입니다.

app/auth/login/page.tsx

google로그인 함수

const signInWithGoogle = async() => {
	const {data, error} = await supabse.auth.signInWithOAuth({
    	provider: 'google',
        options: { queryParams: { access_type: "offline", prompt: "consent" } } // 이건 서버 닫으면 다시 로그인하게 -> 연습용
    });
    
    if (data) {
    console.log("로그인 되었습니다.");
  }
  if (error) {
    console.log("error : ", error);
    setError(error.message);
  }
}

이메일 로그인 함수

const signInWithEmail = async (e: React.FormEvent) => {
  e.preventDefault();
  const { user, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) {
    console.log("error : ", error);
    setError(error.message);
  } else {
    console.log("로그인 되었습니다.", user);
  }
};

피드백

이제 다양한 로그인 방식을 이해했으니 실제에 적용해봐야겠습니다.
ex) 각 사용자 마다의 todo 루틴만들기
ex) 영화 API 불러와서 사용자가 좋아하는 영화 찜하기

요새 빠진 웹툰인 "펜홀더"에 빠져있는데
한번은 실력, 두번부터는 재능이라는 말처럼, 이 기능을 온전히 내껄로 만들어 앞으로 프로젝트를 하는데 적극적으로 사용해보겠습니다!!!

profile
SW로 문제를 해결하려는 열정만 있는 대학생

0개의 댓글