[React] SubwayTour - 3. 로그인하기

이정우·2022년 5월 7일
0

SubwayTour

목록 보기
3/4
post-thumbnail

지하철 역 관리에 무슨 로그인까지 필요하나 싶을지도 모르겠지만, 추후에 사진이나 글귀를 업로드하는 기능을 넣을 수도 있기 때문에 로그인 기능도 구현해야 한다는 생각이 들었다.

직접 로그인 방식을 구현하기보다는 Firebase의 Authentication 기능을 이용하기로 했다.

Authentication

Firebase Auth 설정

왼쪽 메뉴바의 Authentication를 클릭하여 시작하기 버튼을 누르면 설정을 할 수 있다.

다양한 인증 수단이 존재하는데, 나는 구글 로그인만 사용하기로 했다. 다른 사이트에서 Sign in with Google라는 버튼을 통해 구글 계정으로 로그인했던 기억이 한 번쯤은 있을텐데, 지금 사용하고자 하는 것이 바로 그 기능이다.

사용 설정을 ON으로 바꾸면 랜덤하게 프로젝트의 이름이 생성되며 아래의 지원 이메일을 자신의 이메일로 선택하면 사용할 준비가 끝난다.

구글 로그인이 성공적으로 추가된 것을 볼 수 있다.

react-router-dom

아직은 별 페이지가 없지만, 나중에 추가할 것이기 때문에 미리 react-router-dom을 설치해준다.

$ npm i react-router-dom

다음으로 아래 사진과 같이 Component 디렉토리와 Auth.js 파일을 생성했다. 이제부터는 각 페이지를 구성하는 컴포넌트들은 Component 디렉토리에서 관리할 것이다.

Home.js는 홈페이지라는 것만, NotFound.js는 잘못된 경로로 접근했다는 것만 알 수 있도록 간단하게 작성하였다.

// Home.js
const Home = () => {
  return <div>Home Page</div>;
};

export default Home;

// NotFound.js
const NotFound = () => {
  return <>요청하신 페이지를 찾을 수 없습니다.</>;
};

export default NotFound;

로그인을 위해 ID나 계정 등의 정보를 입력할 필요가 없기 때문에, LoginForm은 단순하게 로그인/로그아웃 버튼만 렌더링하도록 만들었다.
user 객체가 존재한다면 로그아웃 버튼이, 존재하지 않는다면 로그인 버튼이 보일 것이다.

const LoginForm = ({ user, setUser }) => {
  const handleLogin = () => {
    console.log("login button clicked!");
  };

  const handleLogout = () => {
    console.log("logout button clicked!");
  };

  return (
    <>
      {user ? (
        <button onClick={handleLogout}>Logout</button>
      ) : (
        <button onClick={handleLogin}>Login</button>
      )}
    </>
  );
};

export default LoginForm;

다음으로 Route.js에서 라우팅 설정을 해주었다.

import { Link, Routes, Route, BrowserRouter as Router } from "react-router-dom";
import Home from "./Component/Home";
import LoginForm from "./Component/LoginForm";
import NotFound from "./Component/NotFound";
import { useState } from "react";

export default () => {
  const [user, setUser] = useState(null);
  
  return (
    <Router>
      <header>
        <Link to="/">
          <button>Home</button>
	      <LoginForm user={user} setUser={setUser} />
        </Link>
      </header>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </Router>
  );
};

현재는 로컬에서 작업을 하고 있기 때문에 https://localhost:3000/에서는 홈페이지가, https://localhost:3000/asdfhttps://localhost:3000/111과 같은 다른 모든 페이지에서는 NotFound 페이지가 나타나게 된다.

  • react-router-dom의 버전이 v6으로 업데이트되면서 많은 부분이 변화하였는데, 기존처럼 사용할 수 없기 때문에 공식문서나 구글링을 통해 알아보는 것을 추천한다.

Google 로그인

Firebase.js에서만 DB 관련 작업을 수행했던 것처럼, 로그인은 Auth.js에서 수행할 것이다.

// Auth.js

import {
  getAuth,
  GoogleAuthProvider,
  signInWithPopup,
  signOut as googleSignOut,
} from "firebase/auth";

const provider = new GoogleAuthProvider();
const auth = getAuth();

export const signIn = async () => {
  signInWithPopup(auth, provider)
    .then((result) => {
	  console.log(result.user);
      setUser(result.user);
    })
    .catch((e) => {
      console.log(e);
    });
};

export const signOut = async () => {
  googleSignOut(auth)
    .then(() => {
      console.log("signOut Success!");
    })
    .catch((e) => {
      console.log("signOut failed");
    });
};

로그인은 signInWithPopup(auth, provider) 메소드를 통해서 수행하는데, 새로운 팝업창에서 구글 로그인이 이루어진다.

로그아웃은 signOut(auth) 메소드를 통해 수행하는데, signOut이라는 이름으로 export하기 위해 googleSignOut이라는 이름으로 import해서 사용하였다.


이제 LoginForm.js에 방금 구현한 로그인/로그아웃 메소드를 적용해보자.

// LoginForm.js
import { signIn, signOut } from "../Auth";

const LoginForm = ({ user, setUser }) => {
  const handleLogin = async () => {
    signIn(setUser);
  };

  const handleLogout = async () => {
    signOut(setUser);
  };

  return (
    <>
      {user ? (
        <button onClick={handleLogout}>Logout</button>
      ) : (
        <button onClick={handleLogin}>Login</button>
      )}
    </>
  );
};

export default LoginForm;

로그인/로그아웃이 성공했다!

특정 유저만 로그인

구글 로그인 기능을 통해 로그인은 쉽게 구현했으나, 문제점이 남아있다. 나와 여자친구가 아니더라도 구글 계정을 가진 모든 사람이 로그인이 가능하다는 것이다.

따라서 Firestore에 user 컬렉션을 하나 생성하여 간단하게 이름과 이메일을 가진 문서 2개를 생성했다.

로그인을 시도할 때마다 이 컬렉션을 참조해서 컬렉션에 등록된 유저만 로그인이 이루어지도록 수정할 것이다.

// Firebase.js
import { ..., getDocs, collection } from "firebase/firestore";

...

const getAuthUsers = async () => {
  const userRef = collection(fireStore, "user");

  return await getDocs(userRef);
};

export default { ..., getAuthUsers };

fireStore에서 user라는 이름을 가진 컬렉션을 userRef에 저장한 뒤, getDocs(userRef)를 통해 데이터를 불러왔다.
RDBMS에서 SELECT * FROM USER 쿼리를 실행했다고 생각하면 된다.


결과를 출력하면 다음과 같이 나온다.

'이 데이터를 어떻게 써야할까' 라는 생각이 들 수도 있지만, 사용 방법은 간단하다. 나는 forEach를 통해 각각의 문서 정보와 로그인한 정보를 비교했다.

// Auth.js

export const signIn = async (setUser) => {
  let curUser;

  await signInWithPopup(auth, provider)
    .then((result) => {
      curUser = result.user;
      console.log(result);
    })
    .catch((e) => {
      console.log(e);
    });

  let isValidUser = false;

  await getAuthUsers().then((users) => {
    console.log(users);

    users.forEach((user) => {
      if (curUser.email === user.data().email) {
        isValidUser = true;
        return;
      }
    });
  });

  if (isValidUser) {
    setUser(curUser);
  } else {
    alert("권한이 없습니다.");
  }
};

아직 비동기 문법을 마스터하지 못해 좀 지저분할 수는 있지만, 코드는 다음과 같은 순서로 실행된다.

  1. 구글 계정으로 로그인
  2. user 문서의 데이터를 불러옴
  3. 불러온 데이터 각각과 구글 로그인 정보를 비교
  4. user 문서 내에 존재하는 로그인 정보일 경우 로그인 성공 / 존재하지 않으면 로그인 실패

위의 영상은 권한을 가진 사용자, 아래는 권한이 없는 사용자인데 생각한대로 잘 작동하는 것을 확인할 수 있다.

소스 보기

0개의 댓글