요약
example/auth에 있는 내용을 참고하여 구현했습니다.
기존의 코드
useEffect를 활용하여 로컬스토리지 토큰을 검사했다, 이때 발생한 문제점이 Home 컴포넌트에서는 로그인을해야 데이터를 불러올 수 있는데 만약 로그인을 하지 않은 유저가 접근했다면 토큰이 없다는 401에러가 뜬 뒤에야 로그인 페이지로 이동한다.
이는 비용적으로, 보안적으로 문제가 될 수 있었기 때문에 수정이 필요하다고 생각했고 react-router 깃헙의 example을 통해 AuthProvider를 만들어 루트를 보호해줄 수 있었다.
const Router = () => {
const location = useLocation();
React.useEffect(() => {
const token = localStorage.getItem("token");
if (token === null && location.pathname !== "/auth")
history.replace("/auth");
}, [localStorage.getItem("token")]);
return (
<Routes>
<Route path="" element={<Home />} />
<Route path="/auth" element={<Auth />} />
<Route path="/auth/signup" element={<SignUp />} />
</Routes>
);
};
export default Router;
해결한 코드
React context API와 react-query의 useMutation을 활용했다. 따라서 커스텀이 필요하다면 공식문서나 구글링을 참고하는게 좋아보인다.
ContextAPI의 Provider를 정의해 토큰이 있는지, 로그인/로그아웃 상태와 상태로직을 전달할 수 있도록 했고 이후 Route를 provider로 감싸줬다
// hooks/AuthProvider.tsx
import React from "react";
import { To, useNavigate } from "react-router-dom";
import { AuthContextType } from "../../types/auth";
import { SignFormType } from "../../types/form";
import { findToken, removeToken, storageToken } from "../../utils/handleToken";
import useSignin from "../queries/Auth/useSignin";
import useSignUp from "../queries/Auth/useSignUp";
export const AuthContext = React.createContext<AuthContextType>(null!);
export function useAuth() {
return React.useContext(AuthContext);
}
function AuthProvider({ children }: { children: React.ReactNode }) {
const [isToken, setIsToken] = React.useState(findToken());
const SignInMutate = useSignin();
const SignUpMutate = useSignUp();
const navigate = useNavigate();
function SignIn(callback?: VoidFunction) {
return function (SignData: SignFormType) {
SignInMutate(SignData, {
onSuccess(response) {
storageToken(response.token);
setIsToken(true);
if (callback) callback();
},
});
};
}
function SignUp(callback?: VoidFunction) {
return function (SignData: SignFormType) {
SignUpMutate(SignData, {
onSuccess(response) {
storageToken(response.token);
setIsToken(true);
if (callback) callback();
},
});
};
}
function LogOut(callback?: VoidFunction) {
return function () {
removeToken();
setIsToken(false);
if (callback) callback();
};
}
function SignNavigateCallback(route: To) {
return function () {
navigate(route, { replace: true });
};
}
const value = {
token: isToken,
SignIn,
SignUp,
LogOut,
SignNavigateCallback,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export default AuthProvider;
const Router = () => {
return (
<AuthProvider>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/auth" element={<Auth />} />
<Route path="/auth/signup" element={<SignUp />} />
<Route path="*" element={<>NULL</>} />
</Routes>
</AuthProvider>
);
};
이후 RequireAuth를 통해 만약 토큰이 없다면 로그인 페이지로 이동하고 토큰이 있다면 해당 컴포넌트를 렌더링하는 고차 컴포넌트를 만들어서 감싸주면 끝이 난다
// RequireAuth.tsx
import React from "react";
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "./AuthProvider";
function RequireAuth({ children }: { children: JSX.Element }) {
const auth = useAuth();
const location = useLocation();
if (!auth.token)
return <Navigate to={"/auth"} state={{ from: location }} replace />;
return children;
}
export default RequireAuth;
const Router = () => {
return (
<AuthProvider>
<Routes>
<Route
index
element={
<RequireAuth>
<Home />
</RequireAuth>
}
/>
<Route path="/auth" element={<Auth />} />
<Route path="/auth/signup" element={<SignUp />} />
<Route path="*" element={<>NULL</>} />
</Routes>
</AuthProvider>
);
};
export default Router;
회고
기능을 구현했을 때 원치않는 동작이 발생한다면 문제로써 정의해야 하는지를 잘 생각해봐야겠다. 몇번 해봤다고 생각해서 대수롭지 않게 넘겼다..
그리고 최근에 과제를 구현한다고 알고리즘 문제를 푼지 꽤 지났는데 다시 꾸준하게 하루에 2문제씩이라도 풀어야겠다
'TIL > 트러블슈팅' 카테고리의 다른 글
UX 향상을 위한 confirm modal 만들기 (0) | 2023.01.17 |
---|---|
redux 오픈소스 코드를 뜯어서 createStore 직접 구현해보기 (0) | 2023.01.16 |
typescript interface property 변환하기, react-query mutate 파라미터 오류 해결, common 컴포넌트로 분리하기 (0) | 2023.01.12 |
typescript interface property를 optional property로 바꾸기 (0) | 2023.01.12 |
typescript 제네릭으로 리팩토링 & 변수,타입 수정 (0) | 2023.01.11 |