(React) App.js에 무분별하게 작성했던 라우팅 설정 코드를 분리하고 Protected Route 전략 도입하기 (2) | Protected Route를 통해 인증된 사용자만 허용하기

kidstone·2025년 2월 6일
0

외개인 프로젝트

목록 보기
8/10
post-thumbnail

들어가며

현재 외개인 프로젝트에서는 로그인 인증된 유저만 접근이 가능한 경로들이 존재한다. 하지만 이에 대해서 별다른 조치를 하고 있지 않은 상황이다. 예를 들어, 알림페이지, 마이페이지, 채팅페이지는 유저만의 정보를 보여주는 것이기 때문에 로그인이 필수적이다. 따라서 Protected Route를 설정하여 인증된 유저만 페이지를 이용할 수 있도록 접근을 제한할 예정이다.

Protected Route란?

Protected Route는 인증된 사용자만 특정 경로에 접근할 수 있도록 하는 방법이다. 인증된 유저만 특정 경로에 접근이 가능하고, 인증되지 않은 유저가 접근시에는 별도의 처리를 하도록 한다.

현재 프로젝트의 로그인 과정

현재 외개인 서비스는 구글 OAuth 방식을 이용해 구글 계정으로 로그인이 가능하다. 로그인 페이지에서 구글 로그인 버튼을 클릭하면, 구글 로그인 페이지로 여러 인증 정보를 담아서 리다이렉트한다. 구글에서 인증을 마치면, 사용자가 리다리엑션되는 페이지로 이동하고, 그 페이지 안에서 쿼리 파라미터로 전달된 code를 통해 구글 API에 요청을 보내서 토큰과 정보를 얻어오면서 로그인을 완료한다. 코드를 보면서 더 자세히 설명하겠다.

LoginPage.jsx

const LoginPage = () => {
	
	const CLIENT_ID =
		'393836402841-kce6okeggrgkkern512g91o39mbb273a.apps.googleusercontent.com';
	const REDIRECT_URL = process.env.REACT_APP_SERVICE_URL;
	return (
		<>
			...
			
			<div className="px-[35px]">
				<Link
					to={`https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URL}/callback&response_type=code&scope=https://www.googleapis.com/auth/userinfo.email`}
					style={{ borderColor: COLOR.gray200 }}
					className="border w-full h-[50px] rounded-[5px] flex items-center px-[23px] shadow-[1px_1px_4px_rgba(0,0,0,0.25)] hover:opacity-20"
				>
					<img
						className="mr-[60px]"
						src={GoogleIcon}
						alt="구글 로그인 아이콘"
					/>
					<StyledButtonText>Google로 계속하기</StyledButtonText>
				</Link>
			</div>
		</>
	);
};

export default LoginPage;

로그인 페이지에서 'Google로 계속하기' 버튼(링크)를 클릭하면 구글 로그인 창으로 이동한다. 이때 파라미터로 여러 인증 정보를 보내게 된다.

이후 구글 로그인 페이지에서 로그인을 완료하면 설정했던 redirect_uri로 이동한다. 외개인 프로젝트는 callback.jsx 페이지로 설정하였다.

callback.jsx

import React, { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { API } from '@utils/api';
import useAuthStore from '@store/authStore';
import useMyInfoStore from '@store/myInfoStore';

const CallBack = () => {
	const setAccessToken = useAuthStore((state) => state.setAccessToken);
	const setMyInfo = useMyInfoStore((state) => state.setMyInfo);
	const location = useLocation();
	const navigate = useNavigate();
	useEffect(() => {
		const queryParams = new URLSearchParams(location.search);
		const code = queryParams.get('code');
		const fetchData = async () => {
			try {
				const response = await API.get(
					`/api/v1/member/auth/google/callback?code=${code}`,
				);
				const accessToken = response.data.accessToken;
				const {
					email,
					gender,
					introduction,
					name,
					photoUrl,
					profileSetUpStatus,
					id,
				} = response.data;
				const myInfo = {
					email,
					gender,
					introduction,
					name,
					photoUrl,
					profileSetUpStatus,
					id,
				};
				setAccessToken(accessToken);
				setMyInfo(myInfo);
				API.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
				if (profileSetUpStatus) {
					navigate('/home');
				} else {
					navigate('/setting');
				}
			} catch (error) {
				navigate('/login');
			}
		};
		if (code) {
			fetchData();
		}
	}, []);
	return <div></div>;
};

export default CallBack;

redirect_uri 경로에 대한 페이지 코드이다. 이 경로로 넘어올때 쿼리 파라미터로 code를 전달받을 수 있다. 그럼 그 code를 이용해서 백엔드에 API를 요청하여 응답값으로 유저 정보와 토큰값 등을 받아오면 된다. 이들을 이 프로젝트에서는 전역상태로 저장하여 관리했다. 전역 상태 accessToken과 myInfo에 저장하였고, 이제 이 accessToken이 존재하면 로그인 인증이 완료되었다는 플래그 변수 역할을 하는 것이다.

ProtectedRoute.jsx 생성

import React from 'react';
import { Navigate } from 'react-router-dom';
import useAuthStore from '../store/authStore';

const ProtectedRoute = ({ children }) => {
    const isAuthenticated = useAuthStore((state) => state.accessToken);


    if (!isAuthenticated) {
        return <Navigate to="/login" replace />;
    }

    return children;
};

export default ProtectedRoute;

그리고 ProtectedRoute 파일을 생성하였다. 전역 상태 accessToken이 존재하면 위에서 받아온 children을 리턴하고, 존재하지 않다면 로그인 페이지로 이동시킨다. 이때, 무한 리다이렉션이 발생하지 않게 하기 위해 replace 속성을 추가했다.

변경된 AppRoutes.jsx

import React from 'react';
import {
	Routes,
	Route,
	useLocation,
	Outlet,
} from 'react-router-dom';

//pages
import ProtectedRoute from './ProtectedRoute';
import LandingPage from '@pages/LandingPage/LandingPage';
...

const AppRoutes = () => {
  return (
    <Routes>
      <Route path="/" element={<MainLayout />}>
      {/* Protected Route 적용 */}
        <Route path="/alarm" element={
          <ProtectedRoute>
            <Alarm />
          </ProtectedRoute>
        } />
        <Route path="mypage" element={
          <ProtectedRoute>
            <MyPage />
          </ProtectedRoute>
        } />
        <Route path="mypage/come-matchingrequests" element={
          <ProtectedRoute>
            <ComeMatchingListPage />
          </ProtectedRoute>
        } />
        <Route path="mypage/roommate-applylist" element={
          <ProtectedRoute>
            <RoommateApplyListPage />
          </ProtectedRoute>
        } />
        <Route path="mypage/like" element={
          <ProtectedRoute>
            <LikePage />
          </ProtectedRoute>
        } />
        <Route path="mypage/mypost" element={
          <ProtectedRoute>
            <MyPostPage />
          </ProtectedRoute>
        } />
        <Route path="/chat" element={
          <ProtectedRoute>
            <Chat />
          </ProtectedRoute>} />
        <Route
          path="/chat/chatroom/:subscribeID"
          element={
          <ProtectedRoute>
            <ChatRoom />
          </ProtectedRoute>}
        />
          
            
        <Route index element={<HomePage />} />
        <Route path="callback" element={<CallBack />} />
        ...나머지 인증이 필요없는 페이지 경로 설정...
      </Route>
    </Routes>
  );
};

function MainLayout() {
	...
}

export default AppRoutes;

그리고 보호가 필요한 경로를 ProtectedRoute 컴포넌트로 감싸면 끝이다!


이것은 로그인 하지 않았을 때

로그인 했을때는 제대로 페이지가 렌더링되는 것을 볼 수 있다.

profile
안녕하세요. 웹 프론트엔드 개발자 앞잡이 '꼬마돌' 입니다.

0개의 댓글