:ledger: Supabase 로그인,로그아웃,회원가입 기능을 추가해보자.

Supabase를 사용하여 리액트에서 로그인,로그아웃,회원가입 기능 추가 방법입니다.

:one: user table 생성 및 참조(Table Editor)

supabase의 좌측 메뉴 Authentication -> Users 를 보면 기본적으로 supabase에서 유저 테이블을 관리를 해준다. 하지만 보안상의 문제로 공식 문서에서 추천하지 않는다. 그렇기에 public 스키마에서 user 테이블을 만들고 auth 스키마에있는 users 테이블을 참조하는 방법으로 진행한다.

:pushpin: 1-1) user 테이블 생성

  • Supabase 프로젝트 좌측 메뉴인 Table Editor에 들어가서 Create a new Table를 클릭하여 생성해준다.

:pushpin: 1-2) user 테이블 세팅

  • 유저 테이블에 필요한 값을 설정해주고 Enable Row Level Security (RLS) 체크를 해제한다.
    • 보안 관련된 내용인데, 명확히 이해하지 못했고 해당 부분을 체크하면 DB에 값이 저장되지 않아서 해제했다. 자세한 내용은 공식문서 에서 확인이 가능하다.

supabase table set1

:pushpin: 1-3) auth의 users 참조 세팅

  • 테이블에 값을 설정했으면, id 옆의 파일 아이콘을 클릭하여, auth.users를 참조할 수 있게 설정해준다.

supabase table set2

:pushpin: 1-4) auth의 users 연동

  • 1-3에서 참조 설정 후 SQL코드를 입력하여 연동해줘야한다.
  • 공식문서를 참고하여 아래 이미지와 같이 연결해주고 이미지에 없지만 SQL문 하단의 Run을 클릭하여 실행시키면 된다.

supabase table set3

:two: auth 설정

supabase 좌측 메뉴의 Authentication를 들어가면 Users 메뉴가 보인다. 이 부분이 supabase에서 관리해주는 유저 테이블이다. [1-3]에서 참조해주었으니 유저가 추가(회원가입)될 때 마다 자동적으로 public 스키마의 user 테이블에도 자동으로 값이 추가된다.

:pushpin: 2-1) email 설정

Authentication 메뉴에 Providers 탭에 들어가면 다양한 로그인 방식이 있다. 기본적으로 Email로 세팅되어있는데, 이 부분에서 반드시 설정해줘야 할 것이 있다. (처음에 이 부분을 몰라서 1시간 넘게 고생함..) 그것은 바로 Confirm email을 해제해줘야 하는데 이 항목이 무엇이냐면 회원가입 할 때 사용자가 메일에서 확인을 해줘야한다는 것이다. 일단 기본 사용법을 연습하고 있던 나는 필요 없는 기능이라 체크를 해제해주었다.

supabase email set

:three: 회원가입 (SignUp.jsx)

:pushpin: 3-1) 작성 코드

// import
import { useState } from "react";
import { supabase } from "../supabaseClient";
import { useNavigate } from "react-router-dom";

const SignUp = () => {
  const navigate = useNavigate();

  // state 처리 : input 의 값을 담아줌
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSignUp = async (e) => {
    // 새로고침 방지
    e.preventDefault();

    // supabase의 signUp 메서드를 상요하여, user의 email, password 값을 추가해줌
    await supabase.auth.signUp({
      email,
      password,
    });

    // 위의 코드가 실행된 후 alert 메세지 출력
    alert("축하합니다! 회원가입이 완료됐어요. 로그인 페이지로 이동할께요.");
    // useNavigate 사용하여, 로그인 페이지로 이동
    navigate("/signin");
  };
};

export default SignUp;

:pushpin: 3-2) 실행 결과

signup 페이지에서 회원가입 버튼을 클릭하면, auth 스키마와 public 스키마의 user가 연동되어 추가된 모습을 볼 수 있다.

auth user

public user

:four: 유저 데이터 전역 관리 (UserContext.jsx)

회원가입 진행 후 로그인을 하려면, DB에 저장된 user의 정보를 가져와야한다. user의 정보는 회원가입 뿐만아니라, 다양한 처리를 해주기 위해 context API를 사용하여 전역으로 관리한다.

:pushpin: 4-1) 작성 코드

// 처음 렌더링될 때 user의 값은 불러져와 있지 않기 때문에 null로 설정한다.
const [user, setUser] = useState(null);

// 컴포넌트가 처음 랜더링될 때 유저의 데이터를 한번만 받아오기 위해 useEffect를 사용한다.
useEffect(() => {
  // 비동기 함수로, Supabase의 getSession 메서드를 사용하여 현재 사용자의 세션 정보를 가져온다
  const getSession = async () => {
    const response = await supabase.auth.getSession();
    // supabase의 메서드를 사용하여, user의 데이터를 state에 저장한다.
    setUser(response.data.session.user);
  };

  getSession();
}, []);

:four: 로그인 (SignIn.jsx)

:pushpin: 4-1) 작성 코드

// import
import { useContext, useState } from "react";
import { supabase } from "../supabaseClient";
import { UserContext } from "../contexts/UserContext";

const SignIn = () => {
  // 현재 사용자의 정보를 가져옴
  const { user } = useContext(UserContext);

  // 로그인한 값을 처리하기 위해 state 생성
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSignIn = async (e) => {
    // 새로고침 방지
    e.preventDefault();

    try {
      //  Supabase의 인증 API를 사용하여 이메일과 비밀번호로 로그인한다.
      await supabase.auth.signInWithPassword({
        email,
        password,
      });
    } catch (error) {
      console.log("로그인 error => ", error);
    }
  };

  return (
    <div>
      <h1>Sign In</h1>
      <form onSubmit={handleSignIn}>
        <input
          type="email"
          placeholder="Email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          placeholder="Password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        <button type="submit">Sign In</button>
      </form>
    </div>
  );
};

export default SignIn;

:five: 로그아웃

로그아웃 처리는 간단하다. supabase의 메서드인 signOut을 사용하여 클릭시 실행하면 된다.

:pushpin: 5-1) 작성 코드

const { setUser } = useContext(UserContext);
const handleSignOut = async () => {
  // 로그아웃
  await supabase.auth.signOut();
  // 로그아웃 이후 상태를 null 변경해줌
  setUser(null);
};

:six: 라우팅 처리

로그인 여부를 확인하여 로그인 되어있을 경우 접근,접근 불가한 페이지를 설정한다.

:pushpin: 6-1) 로그인일 때 접근 불가능하게 처리

const AuthRoute = () => {
  // 유저를 전달 받음
  const { user } = useContext(UserContext);
  if (user) {
    alert("현재 로그인된 상태입니다.");
    // 로그인 되어있을 경우 접속이 불가능한 페이지는 아래와 같이 메인 페이지로 이동시킴
    return <Navigate to="/" />;
  }
  return <Outlet />;
};

// 로그인 되어잇을 경우 로그인,로그아웃 페이지에 접근하면 메인 페이지로 이동
<Route element={<AuthRoute />}>
  <Route path="/signin" element={<SignIn />} />
  <Route path="/signup" element={<SignUp />} />
</Route>;

:pushpin: 6-2) 로그인이 되어있어야 접근 가능하게 처리

const PrivateRoute = () => {
  // 유저를 받아옴
  const { user } = useContext(UserContext);
  if (!user) {
    alert("로그인이 필요한 페이지입니다.");
    // 로그인 페이지로 이동시킴
    return <Navigate to="signin" />;
  }
  return <Outlet />;
};

:fire: 마무리

요번 팀 과제에서 나의 역할에 로그인,로그아웃이 없지만 한 번 구현해보고싶은 마음이 컷었다. supabase를 처음 사용하면서 아무래도 세팅하는 부분이 정말 어려웠던 것 같다.. 이 부분만 잘 세팅되고 데이터를 전달받기만 한다면 개인적으로 70%는 성공한 것 같은…