[내배캠 3기] 팀 프로젝트 Newsfeed - 경로 보호
TIL 23. 11. 23
들어가며
내배캠 뉴스피드 팀 과제 3일차.
프로젝트 기획에 대한 피드백을 받았다.
“로그인한 유저와 아닌 유저의 액션을 상세하게 정해줘야 할 것 같아요. router 단에서 로그인을 검증하고 로그인이 필요한 화면임에도 로그인이 안되어있으면 redirect를 해주는 등의 처리도 필요할 수 있습니다.”
"”다른 팀 및 수강생들의 프로젝트를 모아놓는 서비스라는 특징으로 접근해본다면, 로그인하지 않았을 경우 댓글입력, 게시글 입력 정도만 막고 나머지 조회 등은 자유롭게 해주는 등 처리가 필요할 것 같아요.”
로그인이 필요한 페이지 Redirect
프로젝트 필수 구현 기능 중 글 작성 페이지와 마이 페이지는 로그인 한 사용자만 접근할 수 있어야 한다. 댓글이나 게시글 수정, 삭제의 경우는 전역으로 관리하는 사용자 세션 정보가 있기 때문에 컴포넌트 단에서 처리할 수 있을 것이라 생각했다.
글 작성 페이지와 마이 페이지에 로그인하지 않은 사용자가 접근하면 이전 페이지로 Redirect하도록 구현한다.
처음 애플리케이션에 접근하면 로그인 한 상태라면 다시 로그인할 필요가 없다. 따라서, 로그인된 사용자 세션이 있는지 먼저 확인하고 응답받아 사용자 세션 정보를 전역 상태로 관리하도록 했다.
// App.jsx
import React, { useEffect } from 'react';
import GlobalStyles from 'GlobalStyle';
import Router from 'Router';
import { userStateChange } from 'config/firebase';
import { useLoggedIn } from 'hooks/useAuth';
export default function App() {
const { setLoginState } = useLoggedIn();
useEffect(() => {
userStateChange((user) => setLoginState(user));
}, [setLoginState]);
return (
<>
<GlobalStyles />
<Router />
</>
);
}
useLoggedIn
: 로그인 여부를 확인하는 커스텀 훅setLoginState
: 함수는 로그인 상태를 업데이트 하는 함수
userStateChange
: firebase의onAuthStateChanged
함수를 포함하여 사용자 세션 상태가 변경되면 지정한 콜백함수를 실행한다. 전역으로 관리하는 사용자 세션 정보를 업데이트
로그인한 뒤 Home 페이지에서 전역으로 관리하는 사용자 정보에 접근해 콘솔로 출력해보았다. 처음 애플리케이션에 접근하면 전역으로 관리하는 로그인된 유저 상태 값이 null로 초기화된다.
처음 애플리케이션 접근하여 유저 정보 기다리기
사용자가 처음 애플리케이션에 들어오면 잠시 동안 Firebase SDK는 쿠키와 토큰을 가져와서 사용자의 로그인 여부와 사용자가 누구인지 확인하기 위해 서버와 통신한다.
const auth = getAuth()
처음에 Firebase가 유저 정보를 받아오는 동안 시간이 걸린다. 이 때문에 값이 null로 초기화되었던 것이다.
// Home.jsx
import React from 'react';
import HomeContainer from 'components/Home/HomeContainer';
import { useLoggedIn } from 'hooks/useAuth';
export default function Home() {
const { loginState } = useLoggedIn();
console.log(loginState); // null -> { uid: "1234", email: "abc@naver.com", ... }
return (
<div>
<HomeContainer />
</div>
);
}
loginState
: 현재 사용자 정보
Firebase가 유저 정보를 가져오는 동안에는 사용자 세션 정보가 없기 때문에 접근하지 못하는 페이지(e.g. 글 작성 페이지)도 잠시 동안 보여진다. 이를 방지하기 위해 Firebase에서 유저 정보를 가져오는 동안 기다렸다가 완료되면 화면을 보여주도록 한다.
import React, { useEffect, useState } from 'react';
import GlobalStyles from 'GlobalStyle';
import Router from 'Router';
import { auth, userStateChange } from 'config/firebase';
import { useLoggedIn } from 'hooks/useAuth';
import LoadingScreen from 'components/LoadingScreen/LoadingScreen';
export default function App() {
const [isLoading, setLoading] = useState(true);
const { setLoginState } = useLoggedIn();
const init = async () => {
await auth.authStateReady();
setTimeout(() => setLoading(false), [3000]); // 테스트를 위한 코드
};
useEffect(() => {
init();
}, []);
useEffect(() => {
userStateChange((user) => setLoginState(user));
}, [setLoginState]);
return (
<>
<GlobalStyles />
{isLoading ? <LoadingScreen/> : <Router />}
</>
);
}
auth.authStateReady()
: 최초 인증 상태가 완료되면 resolve된 Promise 반환
auth.authStateReady()
이 완료될 때까지 기다렸다가 isLoading 상태를 false로 변경한다. 로딩중이면 LoadingScreen 컴포넌트를, 로딩이 완료되었다면 Router를 보여줃다.
경로 보호
이제 로그인 되지 않은 사용자에 대해 이전 페이지로 리다이렉트 시켜야 한다. 이는 라우터에서 페이지 컴포넌트를 로그인된 사용자인지 검증하는 컴포넌트로 감싸서 구현할 수 있다.
// Router.jsx
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Layout from 'components/Layout/Layout';
import {
AllBoard,
BoardDetail,
Home,
MyPage,
NewBoard,
ProtectedRoute,
} from 'pages';
export default function Router() {
return (
<BrowserRouter>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/boards" element={<AllBoard />} />
<Route path="/boards/:id" element={<BoardDetail />} />
<Route
path="/boards/new"
element={
<ProtectedRoute>
<NewBoard /> {/* 로그인한 사용자만 접근 가능 */}
</ProtectedRoute>
}
/>
<Route
path="/mypage"
element={
<ProtectedRoute>
<MyPage /> {/* 로그인한 사용자만 접근 가능 */}
</ProtectedRoute>
}
/>
</Routes>
</Layout>
</BrowserRouter>
);
}
전역에서 사용자 정보를 관리하고 있으므로 로그인한 사용자가 있는지 확인한다. 로그인되지 않은 사용자이면 경고창을 띄우고 이전 페이지로 돌아가도록 구현했다.
// ProtectedRoute.jsx
import { useLoggedIn } from 'hooks/useAuth';
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
export default function ProtectedRoute({ children }) {
const { loginState } = useLoggedIn();
const navigate = useNavigate();
useEffect(() => {
if (!loginState) {
window.alert('로그인이 필요합니다.');
navigate(-1);
}
}, [loginState, navigate]);
return <>{children}</>;
}
▶ 기능 테스트
느낀점
Authentication과 관련된 기능을 개발하는데 생각보다 신경 써야 할 요소가 많았다. 앞으로 회원가입과 로그인 구현을 유효성 검사와 서버 응답을 고려하여 최대한 실제 프로덕트와 유사한 수준까지 개발할 생각이다. 인증과 관련 실력을 기를 수 있는 좋은 기회가 될 것 같다.
생각보다 기능 구현이 더뎌서 집중할 필요가 있겠다. 우선순위를 정하고 집중 코딩 시간을 정해 기능 구현에 초점을 둬야 겠다.
댓글남기기