DOCS/troubleshooting/250809_happybell80_OAuth인증시스템.md
happybell80 725ad0876c fix: 문서 파일 실행 권한 제거
- 모든 .md, .html 파일 권한을 644로 정상화
- .gitignore 파일 권한도 644로 수정
- 문서 파일에 실행 권한은 불필요하고 보안상 바람직하지 않음
- deprecated 아이디어 폴더 생성 및 레벨별 UI 변경 아이디어 이동
2025-08-18 00:37:51 +09:00

5.6 KiB

OAuth 인증 시스템 구축 및 CORS 문제 해결

작성일: 2025년 8월 9일 작성자: happybell80

오후 2시 30분

OAuth 인증 플로우 개선 - JWT 토큰 보안 문제

문제 상황

  • 프론트엔드 루트 페이지(/)에 로그인 버튼이 없음
  • OAuth 인증 후 JWT 토큰이 URL 쿼리 파라미터에 노출되는 보안 문제
  • 서버 로그에 토큰이 기록될 위험

해결 과정

  1. 보안 자문 결과

    • JWT 토큰을 URL에 직접 노출하면 안됨
    • 임시 코드 방식 사용 권장
    • Fragment URL (#) 사용으로 서버 로그 노출 방지
  2. Redis 기반 임시 인증 코드 시스템 구현

    # docker-compose.yml
    services:
      redis:
        image: redis:alpine
        container_name: auth-redis
        command: redis-server --appendonly yes
        volumes:
          - redis_data:/data
    
  3. 코드 구현

    • app/core/redis.py: Redis 연결 유틸리티
    • app/routes/auth.py: /auth/verify 엔드포인트 (코드↔토큰 교환)
    • app/providers/gmail.py: Fragment URL 리다이렉트

결과

  • 60초 TTL, 1회용 임시 코드로 보안 강화
  • Fragment URL 사용으로 서버 로그 보안

오후 3시 45분

JWT 모듈 누락 문제

문제 상황

토큰 교환 실패
No module named 'app.core.auth'

원인 분석

  • /app/core/auth.py 파일이 없어서 JWT 토큰 생성 실패
  • create_access_token() 함수 누락

해결 과정

  1. JWT 인증 모듈 생성

    # app/core/auth.py
    from jose import JWTError, jwt  # python-jose 사용
    
    def create_access_token(data: Dict[str, Any]) -> str:
        # JWT 토큰 생성 로직
    
    def decode_access_token(token: str) -> Dict[str, Any]:
        # JWT 토큰 검증 로직
    
  2. 기존 패키지 활용

    • PyJWT 대신 이미 설치된 python-jose 사용
    • 중복 패키지 설치 방지

결과

  • JWT 토큰 생성/검증 정상 작동
  • OAuth 콜백 처리 성공

오후 4시 10분

로그인 후 상태 표시 문제

문제 상황

  • 로그인 성공 후에도 계속 로그인 버튼 표시
  • 사용자 정보가 UI에 반영되지 않음

원인 분석

  1. React 상태 업데이트 타이밍 문제
  2. 비동기 처리로 인한 상태 동기화 실패
  3. URL Fragment 정리 시점과 상태 업데이트 불일치

해결 과정

  1. 프론트엔드 auth-context.tsx 수정

    // 로그인 성공 후 강제 새로고침
    if (data.token) {
      localStorage.setItem('auth_token', data.token);
      setUser({ ... });
      window.location.reload(); // 상태 동기화
    }
    
  2. Header 컴포넌트에 로그인 버튼 추가

    • useAuth 훅 연동
    • 인증 상태에 따른 버튼 토글

결과

  • 로그인 후 페이지 새로고침으로 상태 동기화
  • 사용자 정보 정상 표시

오후 4시 20분

CORS Preflight 요청 실패

문제 상황

OPTIONS /auth/verify 400 Bad Request
CORS preflight 요청 실패
실제 POST 요청이 전송되지 않음

원인 분석

  1. auth-server CORS 설정에 https://ro-being.com 누락
  2. OPTIONS 요청 핸들러 없음
  3. 개발 서버 포트(5173, 5174) 미등록

해결 과정

  1. CORS 허용 도메인 추가

    # app/main.py
    allow_origins=[
        "https://ro-being.com",  # 추가
        "http://localhost:5173",  # 추가
        "http://localhost:5174",  # 추가
        # ... 기존 도메인들
    ]
    
  2. OPTIONS 엔드포인트 추가

    # app/routes/auth.py
    @router.options("/auth/verify")
    async def verify_auth_code_options():
        return JSONResponse(content={}, status_code=200)
    
  3. Request 파싱 수정

    # JSON body 파싱
    body = await request.json()
    code = body.get("code")
    

결과

  • CORS preflight 요청 성공
  • 프론트엔드에서 auth-server API 호출 정상
  • 로그인 플로우 완전 정상화

교훈 및 베스트 프랙티스

1. 보안 우선 설계

  • JWT 토큰은 절대 URL에 노출하지 않기
  • 임시 코드 + Redis 조합으로 안전한 토큰 교환
  • Fragment URL 사용으로 서버 로그 보안

2. CORS 설정 체크리스트

  • 모든 프론트엔드 도메인 등록 필수
  • OPTIONS 요청 핸들러 구현
  • 개발/프로덕션 환경 모두 고려

3. 디버깅 팁

  • 브라우저 개발자 도구 Network 탭 확인
  • Console 로그로 토큰 저장 확인
  • CORS 에러는 서버 로그가 아닌 브라우저에서 확인

4. 상태 관리

  • React 상태 업데이트는 비동기
  • 중요한 상태 변경 후 새로고침 고려
  • localStorage와 React 상태 동기화 주의

5. 모듈 재사용

  • 기존 패키지 확인 후 중복 설치 방지
  • python-jose vs PyJWT 선택 일관성
  • requirements.txt 정기적 점검

최종 시스템 구성

인증 플로우

  1. 사용자 → 로그인 버튼 클릭
  2. Google OAuth 인증
  3. auth-server → Redis에 임시 코드 저장 (60초)
  4. Fragment URL로 리다이렉트 (#auth=코드)
  5. 프론트엔드 → /auth/verify로 코드 전송
  6. auth-server → JWT 토큰 반환
  7. 프론트엔드 → localStorage 저장 & 새로고침

핵심 컴포넌트

  • auth-server: OAuth 프로바이더, JWT 발급
  • Redis: 임시 코드 저장 (TTL 60초)
  • frontend-customer: 로그인 UI, 토큰 관리
  • nginx: 리버스 프록시, SSL 처리

보안 특징

  • JWT 토큰 URL 노출 방지
  • 1회용 임시 코드 (atomic getdel)
  • CORS 화이트리스트 방식
  • Fragment URL로 서버 로그 보안