지하철 역 관리에 무슨 로그인까지 필요하나 싶을지도 모르겠지만, 추후에 사진이나 글귀를 업로드하는 기능을 넣을 수도 있기 때문에 로그인 기능도 구현해야 한다는 생각이 들었다.
직접 로그인 방식을 구현하기보다는 Firebase의 Authentication 기능을 이용하기로 했다.
왼쪽 메뉴바의 Authentication를 클릭하여 시작하기 버튼을 누르면 설정을 할 수 있다.
다양한 인증 수단이 존재하는데, 나는 구글 로그인만 사용하기로 했다. 다른 사이트에서 Sign in with Google라는 버튼을 통해 구글 계정으로 로그인했던 기억이 한 번쯤은 있을텐데, 지금 사용하고자 하는 것이 바로 그 기능이다.
사용 설정을 ON으로 바꾸면 랜덤하게 프로젝트의 이름이 생성되며 아래의 지원 이메일을 자신의 이메일로 선택하면 사용할 준비가 끝난다.
구글 로그인이 성공적으로 추가된 것을 볼 수 있다.
아직은 별 페이지가 없지만, 나중에 추가할 것이기 때문에 미리 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/asdf
나 https://localhost:3000/111
과 같은 다른 모든 페이지에서는 NotFound 페이지가 나타나게 된다.
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("권한이 없습니다.");
}
};
아직 비동기 문법을 마스터하지 못해 좀 지저분할 수는 있지만, 코드는 다음과 같은 순서로 실행된다.
위의 영상은 권한을 가진 사용자, 아래는 권한이 없는 사용자인데 생각한대로 잘 작동하는 것을 확인할 수 있다.