Skip to content

Feature: GitHub OAuth 2.0 로그인 시스템 구현 및 React Query 도입#10

Merged
Monixc merged 10 commits into
developfrom
feature/auth-login
Aug 5, 2025
Merged

Feature: GitHub OAuth 2.0 로그인 시스템 구현 및 React Query 도입#10
Monixc merged 10 commits into
developfrom
feature/auth-login

Conversation

@woo-dev-log

Copy link
Copy Markdown
Contributor

📌 PR 종류 (PR Type)

  • 🐛 버그 수정 (Bugfix)
  • ✨ 기능 추가 (Feature)
  • 🎨 UI 스타일 변경 (Style) - CSS, 클래스명, 레이아웃 등 UI 변경
  • ♻️ 코드 리팩토링 (Refactor) - 기능 변화 없이 코드 구조 개선
  • 📦 패키지/빌드 관련 변경 (Build) - 설정 파일 수정, 의존성 추가 등
  • 🧱 테스트 코드 추가/수정 (Test)
  • 📝 문서 수정 (Docs)
  • 🚧 WIP (작업 중 PR)
  • 기타 (Other):

📝 요약 (Summary)

GitHub OAuth 2.0 기반 로그인 시스템 구현 및 React Query 도입으로 개발자 친화적 인증 환경 구축


🔍 상세 내용 (Describe your changes)

GitHub OAuth 2.0 로그인 시스템 구현

  • LoginPage: GitHub OAuth URL 생성 및 사용자 리다이렉트 기능 구현
  • AuthCallbackPage: OAuth 콜백 처리 및 사용자 상태별 자동 분기 로직
  • CSRF 보호: state 파라미터를 통한 보안 검증 시스템 적용
  • 자동 분기: 백엔드 응답(200: 기존유저, 210: 신규유저)에 따른 페이지 이동

React Query 통합 및 상태 관리 자동화

  • @tanstack/react-query 패키지 도입으로 API 상태 관리 개선
  • useMutation을 활용한 OAuth API 호출 및 자동 로딩/에러 상태 처리
  • OAuth 특성 반영: retry: false 설정으로 인증 코드 재시도 방지
  • 일반 API는 retry: 2 유지하여 네트워크 오류 대응

아키텍처 개선

  • API 함수를 lib/api.ts로 분리하여 관심사 분리 및 재사용성 향상
  • main.tsx에 QueryClientProvider 전역 설정으로 앱 전체 React Query 활용 기반 마련
  • TypeScript 타입 안전성 확보 및 에러 처리 시스템 구축

보안 및 사용자 경험 강화

  • OAuth 2.0 Authorization Code Flow 표준 준수
  • sessionStorage를 활용한 state 검증으로 CSRF 공격 방지

📸 스크린샷 (필수)

Before After
image

✅ 확인 사항 (CheckList)

  • 브라우저에서 정상 동작 확인
  • 기존 기능에 영향 없음
  • 반응형 UI 대응 확인
  • 코드 컨벤션에 맞게 작성됨 (eslint, prettier 등)
  • 테스트 코드 추가/수정 또는 테스트 통과 확인

💬 기타 참고사항 (Optional)

  • 현재 프론트엔드 로직은 완성되었으며, 백엔드 API 배포 완료 시 실제 연동 테스트 및 최종 검증 예정입니다.

- @tanstack/react-query 설치
- GitHub OAuth API 상태 관리 개선 준비
- 향후 API 호출 최적화 및 캐싱 기능 활용 예정
- LoginPage: GitHub OAuth URL 생성 및 리다이렉트 기능
- AuthCallbackPage: OAuth 콜백 처리 및 상태 분기 로직
- React Query useMutation 적용으로 자동 상태 관리
- API 함수 분리 (lib/api.ts)
- main.tsx에 QueryClientProvider 설정

OAuth 2.0 Authorization Code Flow 구현
- CSRF 보호를 위한 state 파라미터 검증
- 기존유저(200) vs 신규유저(210) 자동 분기

React Query 최적화
- OAuth API만 retry: false 설정으로 재시도 방지
- 일반 mutation은 retry: 2 유지
- 전역 QueryClient 설정 최적화
@github-actions github-actions Bot added ⚙️chore 기타 잡무 ✨feature 기능 개발 labels Aug 1, 2025
@woo-dev-log woo-dev-log requested a review from Monixc August 1, 2025 01:16

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Summary of Changes

Hello @woo-dev-log, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust GitHub OAuth 2.0 login system, providing a streamlined and secure authentication experience. Concurrently, it integrates React Query as the primary library for API state management, which standardizes and optimizes data fetching, caching, and error handling throughout the application. These changes lay a solid foundation for future API interactions and user authentication flows.

Highlights

  • GitHub OAuth 2.0 Integration: I've implemented the full client-side flow for GitHub OAuth 2.0. This includes generating the authorization URL and redirecting users from the login page, as well as handling the callback to process the authorization code and determine user status (existing or new user).
  • React Query Adoption: I've integrated @tanstack/react-query to manage API state. This significantly improves data fetching, caching, and error handling. I've configured specific retry policies: retry: false for OAuth mutations to prevent re-attempts on authentication codes, and retry: 2 for general API queries.
  • Enhanced Security & Architecture: To bolster security, I've implemented CSRF protection by validating the state parameter using sessionStorage. Architecturally, I've refactored all API calls into a dedicated src/lib/api.ts file, enhancing organization and reusability across the application.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

안녕하세요. GitHub OAuth 2.0 로그인 기능 구현 및 React Query 도입 PR 리뷰를 맡았습니다.

전반적으로 OAuth 2.0 인증 흐름을 잘 구현하셨고, React Query를 도입하여 API 상태 관리를 개선한 점이 인상적입니다. 코드 구조와 관심사 분리도 잘 이루어졌습니다.

다만, 몇 가지 개선점을 제안드리고자 합니다. 주로 보안 모범 사례, 타입 안정성 강화, 테스트 코드 작성, 코드 가독성 향상에 대한 내용입니다. 특히 .env 파일 관리 방식과 테스트 코드 부재는 프로젝트의 안정성과 보안을 위해 꼭 확인해주시면 좋겠습니다.

자세한 내용은 각 파일에 남긴 코멘트를 참고해주세요.

Comment thread src/lib/api.ts Outdated
Comment on lines +41 to +55
export const fetchLogin = async (code: string) => {
const response = await fetch(`/api/auth/sign-in?code=${encodeURIComponent(code)}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});

if (!response.ok) {
throw new Error(`GitHub 로그인 실패: ${response.status}`);
}

return {
status: response.status,
data: await response.json()
};
}; No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

fetchLogin 함수의 반환 타입에서 dataany로 추론되고 있습니다. 이는 타입 안정성을 저해하며, 프로젝트 스타일 가이드에도 위배됩니다.1

백엔드 API의 응답 명세에 따라 명확한 타입을 정의하고 적용하여 코드의 안정성과 예측 가능성을 높이는 것을 권장합니다.

예시:

interface LoginResponseData {
  accessToken: string;
  isNewUser: boolean;
  // ... 기타 백엔드에서 전달하는 데이터
}

export const fetchLogin = async (code: string): Promise<{
  status: number;
  data: LoginResponseData;
}> => {
  // ... 함수 본문
};

Style Guide References

Footnotes

  1. 스타일 가이드에 따르면, 타입은 최대한 좁혀서 명시적으로 사용해야 합니다. any 타입의 사용은 지양해야 합니다. (link)

import { useMutation } from '@tanstack/react-query';
import { fetchLogin } from '@/lib/api';

export default function AuthCallbackPage() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

새로운 로그인 흐름과 관련된 중요한 로직이 추가되었지만, 이에 대한 자동화된 테스트가 없습니다. 스타일 가이드에 따라 모든 코드에 테스트를 추가해야 합니다.1

특히 AuthCallbackPage 컴포넌트는 code, state, error 파라미터에 따라 분기하는 로직이 있으므로, 각 케이스(성공, CSRF 에러, 인증 코드 없음 등)에 대한 단위/통합 테스트를 추가하여 안정성을 확보해야 합니다.

Style Guide References

Footnotes

  1. 스타일 가이드에 따라 모든 코드는 자동화된 테스트가 작성되어야 합니다. (link)

Comment thread src/lib/api.ts Outdated
export const fetchLogin = async (code: string) => {
const response = await fetch(`/api/auth/sign-in?code=${encodeURIComponent(code)}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

GET 요청에서는 요청 본문(body)이 없으므로 Content-Type 헤더는 실질적인 의미가 없습니다. 서버에게 JSON 형식의 응답을 기대한다는 것을 명시하려면 Accept 헤더를 사용하는 것이 더 정확한 표현입니다.

Suggested change
headers: { 'Content-Type': 'application/json' },
headers: { 'Accept': 'application/json' },

Comment thread src/pages/auth/AuthCallbackPage.tsx Outdated
Comment on lines +27 to +57
useEffect(() => {
const handleCallback = () => {
const code = searchParams.get('code');
const error = searchParams.get('error');
const state = searchParams.get('state');

if (error) {
navigate('/login', { replace: true });
console.error('GitHub OAuth 에러:', error);
return;
}

if (!code) {
navigate('/login', { replace: true });
console.log('GitHub에서 인증 코드를 받지 못했습니다');
return;
}

const storedState = sessionStorage.getItem('github_oauth_state');
if (state !== storedState) {
navigate('/login', { replace: true });
console.log('보안 검증 실패');
return;
}

signInMutation.mutate(code);
sessionStorage.removeItem('github_oauth_state');
}

handleCallback();
}, [searchParams, navigate, signInMutation]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

useEffect 내의 콜백 처리 로직이 다소 복잡합니다. 이 로직을 useAuthCallback과 같은 커스텀 훅으로 분리하면 다음과 같은 이점이 있습니다.

  • 컴포넌트의 가독성 향상
  • 로직의 재사용성 증대
  • 로직을 격리하여 테스트하기 용이해짐

리팩토링을 고려해보시는 것을 추천합니다.

Comment thread src/pages/auth/AuthCallbackPage.tsx Outdated
Comment on lines +33 to +50
if (error) {
navigate('/login', { replace: true });
console.error('GitHub OAuth 에러:', error);
return;
}

if (!code) {
navigate('/login', { replace: true });
console.log('GitHub에서 인증 코드를 받지 못했습니다');
return;
}

const storedState = sessionStorage.getItem('github_oauth_state');
if (state !== storedState) {
navigate('/login', { replace: true });
console.log('보안 검증 실패');
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

인증 과정의 각 실패 케이스에서 console.logconsole.error를 사용하고 있습니다. 이는 개발 중에는 유용하지만, 프로덕션 환경에서는 에러 추적이 어렵습니다. Sentry와 같은 에러 트래킹 도구를 연동하여, 사용자가 겪는 로그인 문제를 체계적으로 수집하고 분석하는 것을 권장합니다.

Comment thread src/pages/auth/LoginPage.tsx Outdated
import { Button } from "@/components/ui/button";

export function LoginPage() {
const handleGitHubLogin = async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

handleGitHubLogin 함수가 async로 선언되었지만 내부에서 await 구문을 사용하지 않고 있습니다. 불필요한 async 키워드는 코드의 의도를 혼동시킬 수 있으므로 제거하는 것이 좋습니다.

  const handleGitHubLogin = () => {

Comment thread src/pages/auth/LoginPage.tsx Outdated
export function LoginPage() {
const handleGitHubLogin = async () => {
try {
const clientId = import.meta.env.VITE_GITHUB_CLIENT_ID;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

세미콜론이 두 개(;;) 연속으로 사용되었습니다. 하나를 제거해주세요.

      const clientId = import.meta.env.VITE_GITHUB_CLIENT_ID;

Comment thread src/pages/auth/LoginPage.tsx Outdated
Comment on lines +12 to +16
const githubAuthUrl = `https://github.com/login/oauth/authorize?` +
`client_id=${clientId}&` +
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
`scope=${scope}&` +
`state=${state}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

URL 쿼리 문자열을 문자열 접합으로 구성하고 있습니다. URLSearchParams 객체를 사용하면 코드가 더 간결해지고, 각 파라미터가 자동으로 URL 인코딩되어 안정성도 높아집니다.

      const params = new URLSearchParams({
        client_id: clientId,
        redirect_uri: redirectUri,
        scope: scope,
        state: state,
      });
      const githubAuthUrl = `https://github.com/login/oauth/authorize?${params.toString()}`;

@github-actions github-actions Bot added the 🔄refactor 코드 리팩토링 label Aug 1, 2025
- API 응답 타입을 Union 타입으로 확장하여 풍부한 데이터 활용
- 커스텀 훅 분리로 비즈니스 로직과 UI 관심사 분리
- 불필요한 try-catch 제거 및 환경변수 검증 추가
- 코드 가독성, 타입 안전성, 재사용성 모두 향상
- .env, .env.local, .env*.local 파일들 Git 추적 제외
woo-dev-log and others added 6 commits August 4, 2025 14:51
API URL 환경변수 설정
- VITE_API_BASE_URL로 API 서버 분리

무한 호출 버그 수정
- useEffect 의존성에서 signInMutation 제거
- useMutation 객체 재생성으로 인한 무한 루프 해결

OAuth 보안 검증 강화
- 성공 시에만 state 삭제하여 재시도 가능성 보장
- API 실패 상황에서도 안전한 state 관리
- hasProcessed 플래그 추가로 일회용 코드 안전 처리
- 백엔드 중복 요청 이슈 해결
- 사용자 정보 구조 타입스크립트 인터페이스 정의
- OAuth 응답에 User 타입 적용으로 타입 안전성 향상
- token에서 accessToken, sessionId로 분리하여 정교한 인증 관리
- localStorage에 access_token, session_id 개별 저장
@github-actions github-actions Bot added the 🐞bugfix 버그 수정 label Aug 5, 2025
@Monixc Monixc merged commit 8bd3be6 into develop Aug 5, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞bugfix 버그 수정 ⚙️chore 기타 잡무 ✨feature 기능 개발 🔄refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants