이번에는 인증 part 1에 이어서 추가적으로 더 보완해야할 점 2가지를 다뤄보도록 하자.
먼저 로그인 상태의 지속 여부이다. 이는 사용자가 로그인을하고 다른페이지에 접속하거나 시간이 서버로 부터 받은 유효한 토큰 시간이 넘어갔을때 처리해줄 필요가 있다.
보통 토큰은 보안때문에 유효기간이 짧게 설정되어 있다.
현재 연결되어있는 서버에는 토큰의 만료시간이 1시간으로 설정되어있다.
그래서 1시간이 지난 뒤 사용자를 로그아웃 시켜주어야 한다.
1시간이 지나면 -> 토큰을 로컬스토리지에서 삭제하고 UI업데이트 한다.
현재 라우터가 정의된것에 따르면 모든 라우터는 RootLayout컴포넌트 하위로 실행되고 있으니 앱이 시작되면 가장먼저 로드될것이기 때문이다.
여기서 useEffect함수를 설정하고 이펙트 함수로는 만약 토큰이 없으면 그냥 리턴하고 있으면 타이머를 설정해 한시간이 지나면 로그아웃 작업이 트리거 되도록 로직을 구성해보자.
useSubmit훅은 계획적으로 양식을 전송하는데 사용하기 때문에 네비게이션에서 정의한 로그아웃 양식을 전송하게 할것이다.
submit(전송할 데이터, Form에서 등록했던 속성이 담긴 객체)
import { useEffect } from "react";
import { Outlet, useLoaderData, useSubmit } from "react-router-dom";
import MainNavigation from "../components/MainNavigation";
function RootLayout() {
const token = useLoaderData();
const submit = useSubmit();
useEffect(() => {
if (!token) {
return;
}
// 토큰이 있다면
setTimeout(() => {
submit(null, { action: "/logout", method: "post" }); // 로그아웃 트리거
}, 1 * 60 * 60 * 1000); // 1시간 후 실행
}, [token, submit]);
return (
<>
<MainNavigation />
<main>
<Outlet />
</main>
</>
);
}
export default RootLayout;
이로써 위 컴포넌트가 렌더링되거나 token혹은 submit함수가 변경되면 실행될것이나, submit함수 자체는 바뀔일이 없으니 토큰의 변경점에따라 업데이트 된다.
하나 체크해봐야할 부분이 있다.
위처럼 토큰이 만료되면 없애는 설정을 했어도 사용자가 페이지를 다시 리로딩 하면 타이머는 초기화되어 10분만 지났어도 50분이 아닌 1시간으로 재설정이 되어버린다는 것!
그래서 실질적인 토큰만료를 관리하고 등록할 필요가 있다.
토큰을 실제로 부여해주는 action이 있는 인증페이지에 가서,
토큰을 받아옴과 동시에 유효시간을 설정해준다.
// login 혹은 signup 생성 action
export async function action({ request }) {
const searchParams = new URL(request.url).searchParams;
const mode = searchParams.get("mode") || "login";
if (mode !== "login" && mode !== "signup") {
throw json({ message: "지원하지 않는 모드입니다." }, { status: 422 });
}
const data = await request.formData();
const authData = {
email: data.get("email"),
password: data.get("password"),
};
const response = await fetch(`http://localhost:8080/${mode}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(authData),
});
if (response.status === 422 || response.status === 401) {
return response; // 응답 오류 메세지를 사용자에게 표시될 수 있도록 오류 응답을 리턴
}
if (!response.ok) {
throw json({ message: "사용자 인증 불가" }, { status: 500 });
}
// 토큰 저장
const resData = await response.json();
const token = resData.token;
localStorage.setItem("token", token);
// 토큰을 받아옴과 동시에 현재 시간으로부터 1시간을 더한 시간을 저장한다 + 로컬스토리지에도
const expiration = new Date();
expiration.setHours(expiration.getHours() + 1);
localStorage.setItem("expiration", expiration.toISOString());
return redirect("/");
}
이렇게 만료시간까지 받아왔다면 토큰을 얻어내는 util함수에서도 만료여부를 확인하도록 수정해야한다.
auth.js
export function getTokenDuration() {
const expirationDateFromStorage = localStorage.getItem("expiration");
const expirationDate = new Date(expirationDateFromStorage); // Date객체로 변환해주어야함
const now = new Date(); // 현재시간
// 토큰 잔여 기간(밀리초)
const duration = expirationDate.getTime() - now.getTime();
return duration;
}
위 로직에서 duration값이 음수가 나온다면 현재시간이 더 큰값이므로 시간이 지났다고 알 수 있으니 이 사실을 가지고 토큰을 불러오는 로직을 수정하자.
export function getAuthToken() {
const token = localStorage.getItem("token");
const tokenDuration = getTokenDuration(); // 만료시간 리턴
// 애초에 토큰이 없으면
if (!token) {
return null;
}
if(tokenDuration < 0){
return "EXPIRED" // 이 문자는 나중에 로그아웃 되는 action을 트리거 해줄것이다.
}
return token;
}
이제 마지막으로 useEffect로직 안에서 토큰 만료시간을 추가해 로그아웃 로직을 실행할 수 있도록 바꿔주면 되겠다.
import { useEffect } from "react";
import { Outlet, useLoaderData, useSubmit } from "react-router-dom";
import MainNavigation from "../components/MainNavigation";
import { getTokenDuration } from "../util/auth";
function RootLayout() {
const token = useLoaderData();
const submit = useSubmit();
useEffect(() => {
if (!token) {
return;
}
// 토큰이 만료가 되면 자동 로그아웃 되게
if (token === "EXPIRED") {
submit(null, { action: "/logout", method: "post" });
return;
}
const tokenDuration = getTokenDuration();
console.log(tokenDuration); // 시간체크
// 토큰이 있다면(딜레이에 남은 유효기간을 첨부해줘야함)
setTimeout(() => {
submit(null, { action: "/logout", method: "post" }); // 로그아웃 트리거
}, tokenDuration);
}, [token, submit]);
return (
<>
<MainNavigation />
<main>
<Outlet />
</main>
</>
);
}
export default RootLayout;
이러면 토큰의 만료시간이 지나기 전까진 useEffect가 실행되지 않을것이니 토큰 여부를 확인하지 않지만, 시간은 흐르고 있으며 만료시간이 다되면 아마 로그아웃이 될것이다.
이렇게 앱에서 인증절차를 추가하여 로그인의 유무에따라 UI를 업데이트 해주었다.