Skip to content

Conversation

@qjatjr29
Copy link
Collaborator

@qjatjr29 qjatjr29 commented Dec 29, 2025

📌 관련 이슈


✅ PR 체크리스트(최소요구조건)

  • 테스트 작성했다.

✨ 작업 개요

🔑 인증 시스템 구현

  • Prisma 스키마 수정
  • JWT 토큰 생성, 검증 (Access Token, Refresh Token)
  • Google OAuth 2.0 전략 및 가드
  • Naver OAuth 2.0 전략 및 가드
  • Kakao OAuth 2.0 전략 및 가드
  • JWT 인증 가드 및 사용자 정보 추출 데코레이터

👤 사용자 관리

  • OAuth 사용자 조회 및 생성 로직
  • 사용자 프로필 조회 엔드포인트
  • 이메일 중복 가입 방지

✅ 개발 환경 수정

  • Prisma 설정 개선 (prisma-client-js)
  • ESLint 규칙 세분화 (테스트 파일 별도 설정)
  • Jest 설정 개선 (테스트 디렉토리 분리)
  • 코드 일관성 개선 (apps/api -> ESM 확장자 제거)

🧹 작업 상세 내용

Prisma 스키마 개선

prisma/schema.prisma

  • 데이터베이스 네이밍 컨벤션 정규화
    • snake_case (DB) ↔ camelCase (스키마)
    • @Map으로 명시적 매핑
  • prisma-client-js 설정으로 자동 생성 관리
  • email 중복 인덱스 제거

JWT 기반 인증 구현

src/auth/jwt/jwt-payload.interface.ts

  • Access Token 페이로드 타입 정의
  • sub (user id), email, provider 포함

src/auth/jwt/jwt.provider.ts

  • generateAccessToken: 사용자 정보 포함 Access Token 생성
  • generateRefreshToken: Refresh Token 생성
  • verifyAccessToken: Access Token 검증
  • verifyRefreshToken: Refresh Token 검증 및 userId 추출
  • 환경 변수 기반 설정 (SECRET, EXPIRES_IN)

Google OAuth 2.0 구현

src/auth/strategies/google.strategy.ts

  • Passport Google OAuth 2.0 전략을 이용했어요.
  • Google 프로필 정보 추출 (email, displayName, profileImageUrl)

src/auth/guards/google-auth.guard.ts

  • Google 로그인 보호 가드

네이버, 카카오도 비슷하게 구현했어요!

네이버같은 경우에는 passport-naver 가 꽤 오래되고 업데이트가 안되어서 직접 profile을 가져오는 형식으로 구현해봤어요.

JWT 인증 가드 및 데코레이터

src/auth/guards/jwt.auth.guard.ts

  • Authorization 헤더에서 Bearer 토큰 추출
  • 토큰 검증 및 request.user에 저장
  • 토큰 없음/유효하지 않음 → UnauthorizedException

src/auth/decorators/current-user.decorator.ts

  • @CurrentUser(): 현재 인증된 사용자 정보 접근
  • @CurrentUser('sub'): 특정 필드만 추출 가능
  • string 타입 userId → number 변환 처리

사용자 서비스 구현

src/users/users.service.ts

  • findOrCreateOAuthUser: OAuth 사용자 조회/생성 (upsert)
    • providerId 기반 조회
    • 이메일 중복 확인 (다른 Provider)
    • ConflictException 발생
  • findById: 사용자 ID로 조회 (비밀번호 제외)

src/users/users.controller.ts

  • GET /users/me: 현재 인증된 사용자 프로필 조회
  • JwtAuthGuard + CurrentUser 데코레이터 활용
// NOTE: 요 친구는 테스트용이라 추후 수정해야 해요!@!
  @Get('me')
  @UseGuards(JwtAuthGuard)
  async getMyProfile(@CurrentUser('sub') userId: number) {
    const user = await this.usersService.findById(userId);
    return user;
  }

⚙️ 개발 환경 수정

jest.config.js

  • roots: ['/src', '/test'] (디렉토리 분리)

이거 테스트폴더에 테스트를 다 한번에 한다고 이해했어서 이렇게 수정해봤어요..!! 맞는지 확인 부탁드립니다 !!!

apps/api/.eslintrc.mjs

  • 테스트 파일 별도 규칙 설정
  • @typescript-eslint/no-unused-vars 활성화 (_ 접두사 무시)
  • Jest Mock 타입 안전성 규칙 완화 (테스트 환경용)

코드 정리

  • ESM import 확장자 제거 (.js 제거)
  • async bootstrap 함수 void 래핑 (floating promise 규칙)

📸 스크린샷 (선택)

구글 로그인

2025-12-29.7.18.40.mov

네이버 로그인

2025-12-29.10.02.51.mov

카카오 로그인 (이미 같은 이메일로 네이버 로그인을 한 경우)

2025-12-30.12.18.12.mov

카카오 로그인

2025-12-30.12.20.31.mov

사용자 조회 (JWT)

2025-12-29.7.15.33.mov

🔍 고민 지점

패키지 분리

나름 패키지 분리해서 책임을 분리해보고자 했는데 맞는지 잘 모르겠어요.. 지금 약간 머리가 멍해져서 다시 확인해보고 ... 피드백 주시면.. 적용할게요

리프레시 토큰

일단 리프레시 토큰 넣어놓긴 했는데 어떻게 관리할지 고민입니다.
다른 분들은 어떤 식으로 관리하셨나요?!?

    1. RDB에 그냥 저장한다.
    1. Redis 사용한다?

이거 때문에 Redis를 사용해야 하나..
그냥 refresh token 없이 할까...

토큰 전달 방식

현재는 그냥 일단 Requestparam으로 accesstoken이나 refreshtoken을 client에 전달을 해주는 형식과 HTTP Header의 Authorization 로 accesstoken 값을 넣어서 요청을 보내면 처리하는 식으로 해줬는데 어떤 방식이 좋을까 고민이에요..

// HttpOnly 쿠키에 토큰을 심어서 전달하기?!?
  res.cookie('accessToken', accessToken, { httpOnly: true });
  res.cookie('refreshToken', refreshToken, { httpOnly: true });

💬 기타 참고 사항

🌍 환경변수!!

apps/api/.env

DATABASE_URL=""

# JWT
JWT_ACCESS_SECRET=""
JWT_REFRESH_SECRET=""
JWT_ACCESS_EXPIRES_IN="15m"
JWT_REFRESH_EXPIRES_IN="7d"

# Frontend URL
FRONTEND_URL="http://localhost:5173"

# Google
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=http://localhost:3000/auth/oauth2/callback/google

# Naver
NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=
NAVER_CALLBACK_URL=http://localhost:3000/auth/oauth2/callback/naver

# Kakao 
KAKAO_CLIENT_ID=
KAKAO_CLIENT_SECRET=
KAKAO_CALLBACK_URL=http://localhost:3000/auth/oauth2/callback/kakao

일단은 제가 예전에 만든 google client 를 이용했는데 다른 provider나 google을 사용하더라도 공통 이메일 같은 것을 만들어야 할 것 같아요!!

📖 문서화

관련 내용에 대해서 아직 문서화를 하지못했어요.. 최대한 빠릴 해볼게요 😢

☑️ Todo

  • Naver, Kakao OAuth2 추가
  • swagger 설정 추가
  • 문서화 빠르게 하기
  • 뭐 있었는데 갑자기 기억안남...헐.. .아직도 기억안남.

💣 Lint 에러

프론트쪽에서 lint 에러가 발생하는 거 같더라구요!! 일단 제가 해결하다가 더 문제가 생길수도 있을거 같아서,, 냅뒀습니다..! ㅜ


- 데이터베이스 네이밍 컨벤션(snake_case)을 스키마(camelCase)와 명시적으로
매핑 하도록 하고 Prisma 클라이언트 프로바이더를 개선했어요.

- prisma-client-js 프로바이더 명시
- 필드명 camelCase 정규화 및 @Map 추가
- 중복 인덱스 제거 (email)
- Passport 기반 Google OAuth 2.0 및 JWT 인증 구현을 위한 의존성 추가
- 인증: @nestjs/jwt, @nestjs/passport, passport-google-oauth20
- 검증: class-validator, class-transformer
- rootDir -> roots로 변경 (src와 test 디렉토리 명시)
- @typescript-eslint/no-unused-vars 규칙 활성화
- 언더스코어(_)로 의도적 미사용 변수 허용
- 함수 매개변수, 변수, catch 에러 모두 적용
- .js 확장자 제거
- bootstrap() 앞에 void 연산자 추가 (floating promise 경고 해결)
- .js 확장자 제거
- PrismaClient 임포트 경로: '@prisma/client' 사용
- JwtPayload 인터페이스 정의
- JwtProvider 서비스 (access, refresh token 생성 및 검증)
- *.spec.ts, *.test.ts, test/**/*.ts에서 type-safety 규칙 완화
- Jest Mock/expect 관련 unsafe 규칙 비활성화
- generateAccessToken, generateRefreshToken 테스트
- verifyAccessToken, verifyRefreshToken 테스트
- Passport Google OAuth 2.0 전략 설정
- 프로필 정보 추출 및 사용자 조회 및 생성 로직 구현
- validate 메서드 테스트
- 생성자: 환경 변수 초기화 확인
AuthController
- GET /auth/oauth2/google: Google 로그인
- GET /auth/oauth2/callback/google: OAuth 콜백 → 토큰 발급 및 리다이렉트

AuthService
- generateTokens: Access, Refresh Token 생성
- reissueAccessToken: Refresh Token으로 새 Access Token 발급
- 토큰 생성 테스트
- 토큰 갱신 테스트
- Authorization 헤더에서 Bearer 토큰 추출 및 검증
- 유효한 토큰 → request.user에 JwtPayload 저장
- 토큰 유효하지 않음 → UnauthorizedException

- @currentuser() 로 현재 사용자 정보 접근
- 필드 선택 가능: @currentuser('sub')
- GET /users/me: JWT로 인증된 사용자 프로필 조회

- findOrCreateOAuthUser: OAuth 사용자 조회/생성
- findById: 사용자 ID로 조회 (비밀번호 제외)
- findOrCreateOAuthUser 테스트
- findById 테스트
- ConfigModule.forRoot 추가 (전역 환경 변수)
- AuthModule, UsersModule 추가
@qjatjr29 qjatjr29 self-assigned this Dec 29, 2025
@qjatjr29 qjatjr29 added backend 백엔드 전반 로직, 서버 구현 작업 API API 설계 및 구현, 엔드포인트 관련 작업 labels Dec 29, 2025
- passport-oauth2 기반 Naver OAuth 전략구현
-  네이버 API로 사용자 정보 조회 프로필 추출 후 사용자 생성
- NaverAuthGuard: Naver 인증 가드 구현
- moduleNameMapper의 @/ 경로 매핑에 src 디렉토리 명시
- '<rootDir>/$1' → '<rootDir>/src/$1'
- jest 테스트에서 @/ import 시 정확한 경로 해석하도록
- validate 메서드 테스트
- API 호출 테스트
- extractGoogleProfile 메서드 추가 (프로필 추출 로직 분리)
- 이메일 필수 검증 하도록 개선 (UnauthorizedException)
- 사용하지 않는 파라미터에 _ 접두사
- Naver OAuth 엔드포인트 추가
- 공통 콜백 로직 handleOAuthCallback으로 추출
- 리다이렉트 URL 생성 로직 buildRedirectUrl으로 분리
- Passport Kakao OAuth2 전략
- 이메일 필수 검증
- KakaoAuthGuard: Kakao 인증 가드
- KakaoStrategy provider 등록
- GET /auth/oauth2/kakao: Kakao 로그인 페이지로 리다이렉트
- GET /auth/oauth2/callback/kakao: Kakao OAuth 콜백 처리
- done 관련 수정 (불필요한 null 인자 제거)
- done(error, null) → done(error)
@mindaaaa
Copy link
Collaborator

💣 Lint 에러
프론트쪽에서 lint 에러가 발생하는 거 같더라구요!! 일단 제가 해결하다가 더 문제가 생길수도 있을거 같아서,, 냅뒀습니다..! ㅜ

이걸 하루만에...! 고생 많으셨어요🔥🔥🔥
말씀해주신 프론트 lint 에러도 확인했습니다!

현재 해당 이슈는 해결해서, 제 로컬 브랜치에 커밋해두었습니다!
다음 PR에 올라갈 예정이에요! (요게 근데 허스키를 써두 lint 에러 상태로 커밋이 되는...거군요...??)

앗 그리고 현재 PR의 TODO로 남겨두신 항목들도
이번 PR에서 이어서 진행하실 계획이실까요?

리뷰를 지금 진행하면 좋을지,
아니면 TODO 작업 이후에 진행하는 게 나을지
알려주시면 그에 맞춰서 리뷰 진행하겠습니다!

@mindaaaa mindaaaa added this to the User Domain & Ownership milestone Dec 30, 2025
@qjatjr29
Copy link
Collaborator Author

현재 해당 이슈는 해결해서, 제 로컬 브랜치에 커밋해두었습니다! 다음 PR에 올라갈 예정이에요! (요게 근데 허스키를 써두 lint 에러 상태로 커밋이 되는...거군요...??)

앗 그리고 현재 PR의 TODO로 남겨두신 항목들도 이번 PR에서 이어서 진행하실 계획이실까요?

리뷰를 지금 진행하면 좋을지, 아니면 TODO 작업 이후에 진행하는 게 나을지 알려주시면 그에 맞춰서 리뷰 진행하겠습니다!

허스키 같은 경우에는 스테이징된 파일(변경 파일)에 대해서만 lint를 확인하게 되어서 그런거 같아요!
저는 백엔드 코드만 건드려서..?!
프론트 작업할 때 허스키를 적용하기 전이였을까요?!?

TODO로 남긴 것들은 하다가 생각난 것들 적어논거라 다음에 이슈를 생성하고 따로 작업할 거 같아요!

@mindaaaa
Copy link
Collaborator

mindaaaa commented Dec 30, 2025

그렇군요! 스테이징 된 것만 확인하는거라 그랬나봐요!
허스키 적용 전 작업들에서 문제가 생겼었습니다!

현재는 그냥 일단 Requestparam으로 accesstoken이나 refreshtoken을 client에 전달을 해주는 형식과 HTTP Header의 Authorization 로 accesstoken 값을 넣어서 요청을 보내면 처리하는 식으로 해줬는데 어떤 방식이 좋을까 고민이에요..

일단 리프레시 토큰 넣어놓긴 했는데 어떻게 관리할지 고민입니다.

저도 두 방식의 차이가 궁금해서 문서들을 조금 찾아봤는데, 정리해보면 대략 이런 트레이드오프가 있는 것 같았습니다.

  • Authorization Header
    • CSRF에는 비교적 안전
    • 다만 XSS 발생 시 토큰 탈취 위험 존재
  • Cookie
    • HttpOnly 설정 시 XSS로부터 보호 가능
    • 대신 CSRF 대응이 필요

best practice 같은 건 존재하지 않고 트레이드 오프의 문제 같은데...

여러 레퍼런스를 훑어봤을 때는

  • Access Token → Authorization Header
  • Refresh Token → HttpOnly Cookie + CSRF 대응

이 조합을 전제로 설명하는 경우가 가장 많아보였어요!

🔖 참고 자료 목록

개인적으로는,

  • Access Token은 지금처럼 header 방식 유지
  • Refresh Token + CSRF 대응은 P1 이후 작업으로(다음 타자에게) 넘겨도 충분하지 않을까 싶습니다🤭

구조 자체가 이미 확장 가능하게 잡혀 있어서,
다음 단계에서 이어받아도 부담이 크지 않을 것 같아요

Copy link
Collaborator

@mindaaaa mindaaaa left a comment

Choose a reason for hiding this comment

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

env.example 파일 하나 생성해주신 후에 머지해주시면 좋을 것 같아요!

@qjatjr29
Copy link
Collaborator Author

개인적으로는,

  • Access Token은 지금처럼 header 방식 유지
  • Refresh Token + CSRF 대응은 P1 이후 작업으로(다음 타자에게) 넘겨도 충분하지 않을까 싶습니다🤭

구조 자체가 이미 확장 가능하게 잡혀 있어서, 다음 단계에서 이어받아도 부담이 크지 않을 것 같아요

좋은 의견 감사합니다!@!

어떤 식으로 토큰을 프론트에서 저장하고 있는지에 따라 달라질 수 있을 것 같아요!!

제가 일단 생각해본 건 아래 방향이긴 했는데 이 부분은 추후 수정해도 될거 같아요!!

사실 refresh token을 사용할지 어떻게 관리할지도 팀원들과 논의가 필요할 거 같은데..!!

Access Token (자주 사용)

  • localStorage + Authorization Header
  • CSRF 자동 보호
  • 짧은 유효기간으로 XSS 피해 최소화

Refresh Token (가끔 사용)

  • HttpOnly Cookie
  • XSS로부터 더 안전
  • 자주 사용 안 되므로 CSRF 위험 낮을 것...이라 생각

@qjatjr29
Copy link
Collaborator Author

env.example 파일 하나 생성해주신 후에 머지해주시면 좋을 것 같아요!

추가했어요~!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API API 설계 및 구현, 엔드포인트 관련 작업 backend 백엔드 전반 로직, 서버 구현 작업

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] OAuth 2.0 기능 구현 (Google, Naver, Kakao)

3 participants