Skip to content

Commit

Permalink
Merge pull request #15 from SW-Palindrome/feature/refresh-token
Browse files Browse the repository at this point in the history
토큰 자동 재발급 기능 구현
  • Loading branch information
milkbottle0305 authored Apr 4, 2024
2 parents a8ce348 + 5fb3ff2 commit 983624b
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 42 deletions.
40 changes: 16 additions & 24 deletions src/pages/PrivateRoute/PrivateRoute.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,48 @@
import { useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { Navigate, Outlet } from "react-router-dom";
import { validateToken } from "../../services/api";
import Loading from "../../components/Loading";

function PrivateRoute({ authentication }) {
/**
* 로그인 했는지 여부
* 로그인 했을 경우 : true 라는 텍스트 반환
* 로그인 안했을 경우 : null or false(로그아웃 버튼 눌렀을경우 false로 설정) 반환
*/
const [isAuthenticated, setIsAuthenticated] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
setIsAuthenticated(true);
setLoading(false);
const checkAuthentication = async () => {
try {
const isAuthenticated = await validateToken(
localStorage.getItem("studitAccessToken"),
);
setIsAuthenticated(isAuthenticated);
} catch (error) {
alert("토큰이 만료되어 로그인이 필요합니다.");
localStorage.removeItem("studitAccessToken");
localStorage.removeItem("studitRefreshToken");
setIsAuthenticated(false);
} finally {
setLoading(false); // 비동기 작업 완료 후 로딩 상태 변경
setLoading(false);
}
};

checkAuthentication();
}, []);

if (loading) {
// 로딩 중일 때는 아무것도 렌더링하지 않음
return <Loading />;
}

if (authentication) {
// 인증이 반드시 필요한 페이지
// 인증을 안했을 경우 로그인 페이지로, 했을 경우 해당 페이지로
return isAuthenticated === null || isAuthenticated === false ? (
<Navigate to="/" />
) : (
<Outlet />
);
if (isAuthenticated === false) {
return <Navigate to="/" />;
} else {
return <Outlet />;
}
} else {
// 인증이 반드시 필요 없는 페이지
// 인증을 안햇을 경우 해당 페이지로 인증을 한 상태일 경우 main페이지로
return isAuthenticated === null || isAuthenticated === false ? (
<Outlet />
) : (
<Navigate to="/home" />
);
if (isAuthenticated === false) {
return <Outlet />;
} else {
return <Navigate to="/home" />;
}
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/pages/Splash/Splash.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import Header from "./components/Header";
import Sentence from "./components/Sentence";
import Footer from "./components/Footer";
import Modal from "./components/Modal";
import Loading from "../../components/Loading";

const StyledSplash = styled.div`
display: flex;
Expand All @@ -16,15 +15,12 @@ const StyledSplash = styled.div`
function Splash() {
const [signinClicked, setSigninClicked] = useState(false);
const [signupClicked, setSignupClicked] = useState(false);
const [isLoading, setIsLoading] = useState(true);

if (isLoading) <Loading />;
return (
<StyledSplash>
<Header
setSigninClicked={setSigninClicked}
setSignupClicked={setSignupClicked}
setIsLoading={setIsLoading}
/>
<Sentence />
<Footer />
Expand Down
11 changes: 10 additions & 1 deletion src/pages/Splash/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ const StyledButton = styled.button`
background-color: #000000;
`;

function Header({ setSigninClicked, setSignupClicked, setIsLoading }) {
function Header({ setSigninClicked, setSignupClicked }) {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);

function logout() {
if (confirm("로그아웃 하시겠습니까?")) {
setIsAuthenticated(false);
Expand All @@ -58,6 +60,9 @@ function Header({ setSigninClicked, setSignupClicked, setIsLoading }) {
);
setIsAuthenticated(isAuthenticated);
} catch (error) {
alert("토큰이 만료되어 로그인이 필요합니다.");
localStorage.removeItem("studitAccessToken");
localStorage.removeItem("studitRefreshToken");
setIsAuthenticated(false);
} finally {
setIsLoading(false);
Expand All @@ -67,6 +72,10 @@ function Header({ setSigninClicked, setSignupClicked, setIsLoading }) {
checkAuthentication();
}, []);

if (isLoading) {
return null;
}

return (
<StyledHeader>
<Link to="/home">
Expand Down
55 changes: 42 additions & 13 deletions src/services/api.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
const BASE_URL = process.env.REACT_APP_API_URL_DEV;

export const validateToken = async (token) => {
export const validateToken = async (accessToken) => {
try {
const response = await fetch(`${BASE_URL}/auth/token-info/${token}`);

if (!accessToken) {
throw Error("Error: No access token");
}
const response = await fetch(`${BASE_URL}/auth/token-info/${accessToken}`);
if (!response.ok) {
// 상태 코드가 200 OK가 아니면 오류 발생
throw new Error("Failed to validate token");
// 상태 코드가 200 OK가 아니면 액세스 토큰이 유효하지 않은 것이므로 갱신시도
return await refreshToken(accessToken);
}
return true;
} catch (error) {
throw Error(error);
}
};

const data = await response.json();

if (data.sub !== null && !isNaN(data.sub)) {
return true; // 토큰이 유효한 경우
} else {
return false; // 토큰이 유효하지 않은 경우
const refreshToken = async (accessToken) => {
try {
var base64Url = accessToken.split(".")[1];
var base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
var jsonPayload = decodeURIComponent(
atob(base64)
.split("")
.map(function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join(""),
);
const id = JSON.parse(jsonPayload)["sub"];
const refreshToken = localStorage.getItem("studitRefreshToken");
const response = await fetch(`${BASE_URL}/auth/token-refresh`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: id,
refresh_token: refreshToken,
}),
});
if (!response.ok) {
throw new Error("Error: Failed to refresh token");
}
const data = await response.text();
localStorage.setItem("studitAccessToken", data);
return true;
} catch (error) {
console.error("Error validating token:", error);
throw error; // 오류 처리
throw Error(error);
}
};

0 comments on commit 983624b

Please sign in to comment.