Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: 로그인 상태 확인 전 페이지 이동 문제 수정 #286

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from

Conversation

areumH
Copy link
Collaborator

@areumH areumH commented Jan 22, 2025

💡 작업 내용

  • 로그인 상태가 확인되기 전 로그인 여부를 판단하여 페이지가 라우트되는 문제 해결

💡 자세한 설명

기존 코드에서는 새로 고침 시 useTokenRefresh 훅 내의 토큰 재발급 api 함수로 토큰을 받아와 로그인을 유지하도록 했으나, 로그인한 상태에서만 접근이 가능한 페이지에서 새로 고침을 했을 경우, 토큰 재발급 함수의 응답이 오기 전에 페이지가 렌더링되어 로그아웃 상태로 인지되는 이슈가 있었습니다.

// store/auth.ts
const initialState: TokenState = {
  accessToken: undefined,
  isRefreshing: true,
};

export const tokenSlice = createSlice({
  name: 'token',
  initialState,
  reducers: {
    setToken: (state, action: PayloadAction<string>) => {
      state.accessToken = action.payload;
      state.isRefreshing = false;
    },
    deleteToken: (state) => {
      state.accessToken = undefined;
      state.isRefreshing = false;
    },
    setRefreshing: (state, action: PayloadAction<boolean>) => {
      state.isRefreshing = action.payload;
    },
  },
});
  • 이를 해결하기 위해 토큰 값만을 전역 상태로 관리하던 리덕스에 토큰을 재발급 받는 중인지에 대한 여부 값을 추가했습니다.
  • isRefreshing은 기본적으로 true 값을 가지며, api 요청의 응답이 온 후에는 false 값을 가지게 됩니다.

// App.tsx
const isTokenRefreshing = useNewSelector(selectIsRefreshing);
if (isTokenRefreshing) return null;
  • isTokenRefreshing 값이 true인 경우는 아직 토큰 재발급 api 응답이 오지 않은 상태이므로 페이지에 아무것도 렌더링되지 않도록 구현했습니다.
  • 페이지에 다른 ui를 보여주는 것보다 응답이 오기 전까진 흰 화면을 보여주는 것이 일반적이라 생각되어 null로 리턴되도록 구현하였는데, 다른 아이디어가 있으시거나 코드에 문제가 있다면 리뷰 부탁드립니다 🥹🥹🥹

📗 참고 자료 (선택)

📢 리뷰 요구 사항 (선택)

🚩 후속 작업 (선택)

✅ 셀프 체크리스트

  • PR 제목을 형식에 맞게 작성했나요?
  • 브랜치 전략에 맞는 브랜치에 PR을 올리고 있나요? (master/main이 아닙니다.)
  • 이슈는 close 했나요?
  • Reviewers, Labels, Projects를 등록했나요?
  • 작업 도중 문서 수정이 필요한 경우 잘 수정했나요?
  • 테스트는 잘 통과했나요?
  • 불필요한 코드는 제거했나요?

closes #281

Summary by CodeRabbit

Summary by CodeRabbit

  • 새로운 기능

    • 토큰 새로고침 상태 관리 기능 추가
    • 애플리케이션 렌더링 중 토큰 새로고침 프로세스 제어
  • 버그 수정

    • 토큰 새로고침 중 애플리케이션 UI 표시 방지
    • 토큰 상태 관리 로직 개선
  • 리팩터링

    • 인증 상태 관리 프로세스 최적화
    • Redux 스토어의 토큰 상태 추적 메커니즘 업데이트

@areumH areumH added ✔︎pull requests pull requests 코드 체크 요청 ✨feature 구현, 개선 사항 관련 부분 👩🏻‍💻 frontend 프론트엔드 작업 🗝️로그인 labels Jan 22, 2025
@areumH areumH requested review from WonJuneKim and alwubin January 22, 2025 14:39
@areumH areumH self-assigned this Jan 22, 2025
Copy link
Contributor

coderabbitai bot commented Jan 22, 2025

Walkthrough

이 풀 리퀘스트는 토큰 새로 고침 상태 관리를 개선하기 위한 변경 사항을 포함하고 있습니다. src/store/auth.tsisRefreshing 상태를 추가하여 토큰 새로 고침 프로세스를 더 명확하게 추적할 수 있게 되었습니다. App 컴포넌트와 useTokenRefresh 훅도 이에 맞춰 수정되어 토큰 새로 고침 중 애플리케이션의 렌더링을 제어합니다.

Changes

파일 변경 요약
src/store/auth.ts - TokenStateisRefreshing 추가
- 초기 상태에 isRefreshing: true 설정
- setRefreshing 리듀서 추가
- selectIsRefreshing 셀렉터 도입
src/App.tsx - selectIsRefreshing 셀렉터 사용
- 토큰 새로 고침 중 렌더링 방지
src/components/Routes/ProtectedRoutes.tsx - useTokenRefresh 훅 호출 제거
src/hooks/common/useTokenRefresh.ts - 토큰 새로 고침 로직 간소화
- 쿼리 무효화 로직 제거

Assessment against linked issues

목표 해결 여부 설명
로그인 상태 확인 전 페이지 이동 문제 해결 [#281]

Possibly related PRs

Suggested reviewers

  • hsgh085
  • alwubin
  • WonJuneKim

Poem

🐰 토큰의 춤, 새로운 리듬
상태는 흐르고 페이지는 기다려
리프레시의 마법, 조용히 노래해
보안의 토끼가 춤추네 🎉


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44aeb67 and 062f176.

📒 Files selected for processing (1)
  • src/hooks/common/useTokenRefresh.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/common/useTokenRefresh.ts

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/store/auth.ts (1)

26-28: 리듀서 네이밍 개선 제안

setRefreshing 리듀서의 이름이 다소 모호할 수 있습니다.

다음과 같이 더 명확한 이름을 사용하는 것을 제안합니다:

-    setRefreshing: (state, action: PayloadAction<boolean>) => {
+    setTokenRefreshingStatus: (state, action: PayloadAction<boolean>) => {
      state.isRefreshing = action.payload;
    },
src/App.tsx (1)

53-53: 토큰 갱신 중 UI 렌더링 방지

토큰 갱신 중에는 null을 반환하여 UI 렌더링을 방지하는 것은 좋은 접근입니다. 하지만 사용자 경험 개선이 필요합니다.

토큰 갱신 중임을 사용자에게 알리는 로딩 인디케이터를 추가하는 것을 제안합니다:

-  if (isTokenRefreshing) return null;
+  if (isTokenRefreshing) {
+    return (
+      <div className="flex items-center justify-center h-screen">
+        <CircularProgress />
+      </div>
+    );
+  }

Also applies to: 65-66

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ceaede4 and 79812fc.

📒 Files selected for processing (4)
  • src/App.tsx (3 hunks)
  • src/components/Routes/ProtectedRoutes.tsx (0 hunks)
  • src/hooks/common/useTokenRefresh.ts (1 hunks)
  • src/store/auth.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • src/components/Routes/ProtectedRoutes.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: storybook
🔇 Additional comments (3)
src/hooks/common/useTokenRefresh.ts (1)

16-19: 토큰이 존재할 때 불필요한 API 호출 방지

토큰이 이미 존재하는 경우 불필요한 API 호출을 방지하고 즉시 refreshing 상태를 false로 설정하는 최적화가 잘 되어있습니다.

src/store/auth.ts (2)

6-6: 토큰 갱신 상태 추적을 위한 상태 추가

TokenState 인터페이스에 isRefreshing 상태를 추가하고 초기값을 true로 설정한 것은 적절합니다. 이를 통해 앱 초기 로딩 시 토큰 갱신 프로세스를 명확하게 추적할 수 있습니다.

Also applies to: 11-11


20-20: 토큰 설정/삭제 시 자동 상태 업데이트

토큰이 설정되거나 삭제될 때 자동으로 isRefreshing을 false로 설정하는 것은 상태 일관성 유지에 도움이 됩니다.

Also applies to: 24-24

Comment on lines +26 to +27
} finally {
dispatch(tokenActions.setRefreshing(false));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

토큰 갱신 상태 관리 개선 필요

finally 블록에서 refreshing 상태를 false로 설정하는 것은 좋지만, 에러 발생 시에도 동일하게 처리되어 사용자에게 잘못된 상태를 보여줄 수 있습니다.

다음과 같이 에러 처리를 개선하는 것을 제안합니다:

      } catch (error) {
        dispatch(tokenActions.deleteToken());
+       throw error; // 상위에서 에러 처리할 수 있도록 전파
      } finally {
        dispatch(tokenActions.setRefreshing(false));
      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} finally {
dispatch(tokenActions.setRefreshing(false));
} catch (error) {
dispatch(tokenActions.deleteToken());
throw error; // 상위에서 에러 처리할 수 있도록 전파
} finally {
dispatch(tokenActions.setRefreshing(false));

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/components/Routes/ProtectedRoutes.tsx (1)

6-6: Props 타입과 로직이 개선되었습니다.

boolean 플래그 대신 토큰 존재 여부를 직접 확인하는 방식으로 변경된 것이 더 명확하고 안전합니다. 하지만 몇 가지 개선사항을 제안드립니다:

  1. token 타입에 대한 명시적인 상수 정의가 있으면 좋을 것 같습니다.
  2. 리다이렉션 시 현재 경로를 state로 저장하여 로그인 후 원래 페이지로 돌아올 수 있게 하면 UX가 개선될 것 같습니다.

다음과 같이 수정하는 것을 제안드립니다:

 import React from 'react';
 import { PATH } from '@/constants/path';
-import { Navigate, Outlet } from 'react-router-dom';
+import { Navigate, Outlet, useLocation } from 'react-router-dom';
+
+const TOKEN_TYPE = 'string | undefined' as const;
 
 type Props = {
-  token: string | undefined;
+  token: typeof TOKEN_TYPE;
 };
 
 const ProtectedRoutes = ({ token }: Props) => {
-  return token ? <Outlet /> : <Navigate to={PATH.LOGIN} />;
+  const location = useLocation();
+  return token ? (
+    <Outlet />
+  ) : (
+    <Navigate to={PATH.LOGIN} state={{ from: location }} replace />
+  );
 };

Also applies to: 9-10

src/App.tsx (2)

27-27: 상태 관리 로직이 개선되었습니다.

Redux 상태 관리가 더 명확해졌습니다. 하지만 몇 가지 개선사항을 제안드립니다:

  1. 선택자 사용 시 메모이제이션을 적용하면 좋을 것 같습니다.
  2. 타입 안전성을 위해 선택자의 반환 타입을 명시적으로 정의하면 좋을 것 같습니다.
// auth.selectors.ts
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from '../store';

export const selectAuth = (state: RootState) => state.auth;

export const selectAccessToken = createSelector(
  [selectAuth],
  (auth): string | undefined => auth.accessToken
);

export const selectIsRefreshing = createSelector(
  [selectAuth],
  (auth): boolean => auth.isRefreshing
);

Also applies to: 52-53


65-66: 토큰 갱신 중 UI 처리가 개선되었습니다.

토큰 갱신 중에 빈 화면을 보여주는 것이 잘못된 상태를 보여주는 것보다 낫습니다. 하지만 사용자 경험을 위해 다음과 같은 개선을 제안드립니다:

  1. 로딩 인디케이터를 추가하여 사용자에게 진행 상황을 알려주면 좋을 것 같습니다.
  2. 토큰 갱신이 실패할 경우에 대한 처리도 추가하면 좋을 것 같습니다.
+ import { CircularProgress } from '@mui/material';
+ import { styled } from '@mui/material/styles';
+
+ const LoadingContainer = styled('div')({
+   display: 'flex',
+   justifyContent: 'center',
+   alignItems: 'center',
+   height: '100vh',
+ });
+
- if (isTokenRefreshing) return null;
+ if (isTokenRefreshing) {
+   return (
+     <LoadingContainer>
+       <CircularProgress />
+     </LoadingContainer>
+   );
+ }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79812fc and 44aeb67.

📒 Files selected for processing (2)
  • src/App.tsx (4 hunks)
  • src/components/Routes/ProtectedRoutes.tsx (1 hunks)
🔇 Additional comments (1)
src/App.tsx (1)

99-99: 보호된 라우트 구성이 개선되었습니다.

토큰 기반의 인증 확인이 잘 구현되었습니다. 하지만 몇 가지 보안 관련 개선사항을 제안드립니다:

  1. 토큰 만료 시간을 확인하여 만료된 토큰으로 인한 불필요한 API 호출을 방지하면 좋을 것 같습니다.
  2. 민감한 작업(예: 프로필 변경)에 대해서는 추가적인 인증 체크를 고려해보면 좋을 것 같습니다.

토큰 검증 로직을 다음과 같이 추가하는 것을 제안드립니다:

// hooks/useTokenValidation.ts
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { PATH } from '@/constants/path';
import { parseJwt } from '@/utils/jwt';

export const useTokenValidation = (token?: string) => {
  const navigate = useNavigate();

  useEffect(() => {
    if (!token) return;

    const tokenData = parseJwt(token);
    if (!tokenData || tokenData.exp * 1000 < Date.now()) {
      navigate(PATH.LOGIN);
    }
  }, [token, navigate]);
};
✅ Verification successful

토큰 검증 로직 개선 제안이 적절합니다

현재 구현된 인증 시스템에 대한 검토 결과, 제안하신 개선사항이 보안성 강화에 실질적인 도움이 될 것으로 판단됩니다. 현재 구현에는 다음과 같은 부분이 누락되어 있습니다:

  • 토큰 만료 시간 검증
  • 중요 작업에 대한 추가 보안 검증

제안하신 useTokenValidation 훅 구현은 이러한 보안 취약점을 효과적으로 보완할 수 있을 것으로 보입니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Search for JWT-related utilities and token validation
echo "=== Searching for JWT utilities ==="
rg "parseJwt|jwt" -t ts -t tsx

echo -e "\n=== Looking for ProtectedRoutes implementation ==="
ast-grep --pattern 'const ProtectedRoutes = $_'

echo -e "\n=== Searching for token validation logic ==="
rg "token.*expir|expir.*token" -t ts -t tsx

echo -e "\n=== Looking for authentication hooks ==="
fd "use.*auth|use.*token" -e ts -e tsx

Length of output: 933


Script:

#!/bin/bash
echo "=== Examining useTokenRefresh hook ==="
cat src/hooks/common/useTokenRefresh.ts

echo -e "\n=== Examining useAuth hook ==="
cat src/hooks/login/useAuth.ts

echo -e "\n=== Looking for token types and interfaces ==="
rg "interface.*Token|type.*Token" -t ts

Length of output: 2053

Copy link
Collaborator

@WonJuneKim WonJuneKim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refreshing이라는 상태를 사용하는 건 생각하지 못했네요!

화면의 경우 lazy loading 과 suspend 부분을 사용하는 것도 방법 중에 하나일 거같아요(null 값에 대한 대체)

Comment on lines 24 to 26
await queryClient.invalidateQueries({
queryKey: ['members'],
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 재발급 받은 useMemberQuery를 무효화하는 작업이라 흐름 상 새로운 발급 받은 token에 대해서 새로 useMemberQuery가 실행되는게 적절해보이는데 삭제하신 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전에 원준님께서 헤더의 회원 정보 api 관련으로 말씀하셨던 부분을 이 부분으로 착각했네요,,,,😲
062f176 에서 다시 되돌려놓았습니다!!!!!!!!

Comment on lines +9 to +10
const ProtectedRoutes = ({ token }: Props) => {
return token ? <Outlet /> : <Navigate to={PATH.LOGIN} />;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

토큰 값을 가지고 분기를 나눈게 좋은 처리인거 같습니다!

@@ -61,6 +62,8 @@ const App: React.FC = () => {
}, [location.search, navigate]);
useTokenRefresh();

if (isTokenRefreshing) return null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 이 부분에서 null이 반환된 이후에는 수동으로 새로고침을 해줘야하나요? 아니면 자동으로 리렌더링이 발생하나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로컬에서 테스트해본 결과 페이지가 정상적으로 잘 떠서 로그인 후에 새로고침해도 자동 리렌더링될 것 같아요!!

Copy link
Collaborator

@alwubin alwubin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그인 .. 정말 수고하셨습니다👍 많은 고민의 흔적이 보이네요...

아름님께서 고민하신 부분에 대해서 제 생각을 말씀드리자면 현재 구현된 빈화면도 좋지만, 혹시 로딩 스피너를 추가하는건 어떨까용? 물론 그 null인 순간이 길진 않겠지만 진행중임을 보여줄 수 있는 UX 요소를 추가해도 좋을거 같습니닷..!

@@ -3,10 +3,12 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit';

interface TokenState {
accessToken: string | undefined;
isRefreshing: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전역 상태에 토큰 재발급 상태 관리를 통해 재발급 중임을 명확히 할 수 있어서 좋은 방식인거 같습니다!

@areumH
Copy link
Collaborator Author

areumH commented Jan 23, 2025

로그인 .. 정말 수고하셨습니다👍 많은 고민의 흔적이 보이네요...

아름님께서 고민하신 부분에 대해서 제 생각을 말씀드리자면 현재 구현된 빈화면도 좋지만, 혹시 로딩 스피너를 추가하는건 어떨까용? 물론 그 null인 순간이 길진 않겠지만 진행중임을 보여줄 수 있는 UX 요소를 추가해도 좋을거 같습니닷..!

@alwubin 어느 방식이 더 좋은 방식일지 고민이 되네요🥹 스피너를 추가하게 된다면 디자이너, pm님과 간단히 의논이 필요할 것 같기도 하네요!! 그 전에 원준님께서 제안주신 방식에 대해서도 프론트끼리 상의해봐도 좋을 것 같아용👾👾

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨feature 구현, 개선 사항 관련 부분 👩🏻‍💻 frontend 프론트엔드 작업 ✔︎pull requests pull requests 코드 체크 요청 🗝️로그인
Projects
Status: Todo
Development

Successfully merging this pull request may close these issues.

로그인 상태 확인 전 페이지 이동 문제 수정
3 participants