Skip to content

[Work 49] 로그인/로그아웃 기능을 구현했습니다. - #21

Open
sangYuLv wants to merge 22 commits into
developfrom
WORK-49
Open

[Work 49] 로그인/로그아웃 기능을 구현했습니다.#21
sangYuLv wants to merge 22 commits into
developfrom
WORK-49

Conversation

@sangYuLv

@sangYuLv sangYuLv commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

JIRA

📝 작업 내용

📌 요약

  • Apple Sign In → Firebase Auth → Firestore 유저 문서 생성 로그인 흐름 구현
  • 앱 재실행 시 자동 로그인 (깜빡임 없이 즉시 홈 화면 진입)
  • 마이페이지 로그아웃 기능 추가
  • 실기기 테스트를 위한 Firebase 에뮬레이터 네트워크 설정

🔍 상세

[1] 로그인 흐름

사용자가 로그인 버튼을 누르면 아래 순서로 진행됩니다.

  1. AppleSignInProvider가 Apple 인증 시트를 띄우고, 완료되면 idToken + nonce를 반환
  2. FirebaseAuthRepository가 해당 credential로 Firebase Auth에 로그인하고, uid를 반환
  3. FirestoreUserRepositoryusers/{uid} 문서를 조회하여 없으면 생성, 있으면 그대로 반환
  4. 홈 화면으로 전환

이 흐름을 SignInUseCase가 조합합니다.

[2] Domain 레이어 — 인증 제공자에 독립적인 추상화

  • SignInService: 로그인 프로토콜 (Apple 의존 없음)
  • SignInCredential: idToken, nonce, nickname을 담는 DTO
  • SignInError: 사용자 취소 등 로그인 고유 에러
  • AuthRepository: Firebase Auth 세션 관리 (signIn, signOut, currentUserID)
  • UserRepository: Firestore 유저 문서 CRUD (createUserIfNeeded, fetchUser)
  • SignInUseCase / SignOutUseCase

[3] Data 레이어

  • AppleSignInProvider: SignInService 구현체. Apple 인증 시트를 띄워 사용자 인증을 받고, nonce 생성 · SHA256 해싱 · credential 추출까지 처리하여 SignInCredential을 반환
  • FirebaseAuthRepository: OAuthProvider.appleCredential로 Firebase Auth 로그인
  • FirestoreUserRepository: runTransaction으로 유저 문서 존재 여부 확인 후 생성

[4] Presentation 레이어

  • LoginViewModel: signInUseCase.execute() 호출, @Published loginResult로 결과 발행
  • LoginViewController: 버튼 탭 → viewModel.signIn() 한 줄. Combine으로 결과 바인딩

[5] 자동 로그인
SceneDelegate에서 Auth.currentUserID를 동기적으로 확인하여 있으면 처음부터 홈 화면을 root로 설정.
백그라운드에서 Firestore 유저 문서를 검증하고, 실패 시 로그인 화면으로 전환.

[6] 마이페이지 로그아웃
기타 카드에 로그아웃 행 추가. 확인 알럿 후 SignOutUseCaseswitchToLogin().
MyPageViewController를 외부 주입 방식(init(viewModel:))으로 변경.

[7] 에뮬레이터 네트워크 설정
실기기에서 에뮬레이터에 접속할 수 있도록 호스트를 localhost → IP 주소로 변경하고, firebase.json"host": "0.0.0.0" 추가.
Auth만 실환경 사용 (에뮬레이터가 Apple OAuth token 검증 불가).

💬 리뷰 노트

Domain이 Apple을 모르는 구조

Domain의 SignInServiceidToken + nonce + nickname만 반환하는 프로토콜이라 AuthenticationServices를 import하지 않습니다.
Apple 고유 로직(nonce 생성, SHA256 해싱, ASAuthorizationController 제어)은 Data 레이어의 AppleSignInProvider에만 있습니다.
프레젠테이션 계층은 이미 애플 로그인 컴포넌트를 사용해서 apple 로그인에 의존하는 것을 알고 있지만, 도메인이 모르도록 해두면 다른 로그인 서비스를 도입할 때 사용할 수 있겠다고 생각했습니다.

유저 문서 생성에 트랜잭션 사용

FirestoreUserRepository.createUserIfNeededrunTransaction 안에서 문서 존재 여부를 확인하고 없을 때만 생성합니다.
읽기와 쓰기 사이에 다른 요청이 끼어들어 중복 문서가 만들어지는 걸 방지하기 위함입니다.

Auth만 실환경을 쓰는 이유

Firebase Auth 에뮬레이터는 실제 Apple OAuth token을 검증하지 못해 인증 후 실패합니다.
Firestore/Database/Storage 에뮬레이터는 token 검증이 필요 없어 그대로 사용합니다.

👉 결론: Auth만 에뮬레이터 연결 제거, 나머지는 에뮬레이터 유지

에뮬레이터 호스트 IP

AppDelegate.emulatorHost에 로컬 네트워크 IP가 하드코딩되어 있습니다. 기기마다 다르므로 본인 IP로 변경해야 합니다. (ipconfig getifaddr en0)

추후 작업

  • coordinator + screenFactory 작업에 맞게 화면 조립 코드 수정
  • 프로필 이미지를 Firebase Storage에서 가져오도록 전환
  • Firebase 루트 파일 정리
  • 푸시 알림 토큰 작업

📸 영상 / 이미지

WORK-49.loginout.MP4

@github-actions github-actions 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.

🤖 AI 코드 리뷰 - 버그 관점

Apple Sign In 전체 흐름(인증 → Firestore 유저 생성 → 화면 전환)을 구현한 PR이다. 도메인 레이어 분리, DI 구성, 자동 로그인 검증 등 구조는 명확하다. 다만 몇 가지 실제 버그 가능성이 있는 지점이 확인된다: nonce 바이어스 문제, 강제 언래핑, continuation 중복 resume 가능성이다.

Comment thread WhereAreYou/WhereAreYou/Data/AppleSignInProvider.swift Outdated
Comment thread WhereAreYou/WhereAreYou/Data/FirestoreUserRepository.swift Outdated
Comment thread WhereAreYou/WhereAreYou/Data/AppleSignInProvider.swift Outdated

@snughnu snughnu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

수고하셨습니다 🙊

Domain/Data 레이어를 잘 독립시킨거 같아요!
역할과 계층 분리에 신경 쓴 만큼, 아키텍처 리뷰도 한 번 받아보는거 어떨까 싶습니다.

Comment on lines +29 to +30
// 로컬 네트워크 IP — 기기마다 다르므로 본인의 IP로 변경하여 사용 (터미널: ipconfig getifaddr en0)
private static let emulatorHost = "192.168.45.234"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

.xcconfig 등 환경변수로 빼서 관리하는건 어떨까요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

적용했습니다!
아래 화면처럼 환경 변수를 성훈님 IP로 저장해주시면 됩니다.

스크린샷 2026-09-11 00 26 18

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LoginViewModel.signIn() -> SignInUseCase.execute() -> AppleSignInProvider.signIn()
이 흐름에서, 로그인 버튼을 빠르게 두 번 탭하면 Task가 중복으로 발생하는 문제가 있을 것 같아요.

VM에서 플래그를 두거나, 버튼을 한 번 탭했을 때 비활성화 하거나 등의 방법으로 막아두는건 어떨까요?

@sangYuLv sangYuLv Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

플래그 두는 방식으로 수정했습니다!
참고 커밋

Comment on lines 31 to 39
private func validateUser(userID: String) {
let userRepository: UserRepository = DIContainer.shared.resolve()
Task { @MainActor in
guard let _ = try? await userRepository.fetchUser(userID: userID) else {
switchToLogin()
return
}
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

fetchUser에서 .notFound, .network, .unknow을 던지도록 구분했던거 같은데,
여기서도 do/catch로 바꿔서 로그아웃/재시도 등으로 구분하는건 어떨까요

@sangYuLv sangYuLv Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

적용했습니다!
네트워크만 분기 처리한 이유는 네트워크 에러일 때만 재시도로 복구가 가능하기 때문입니다.
notFound 같은 에러는 따로 알럿 없이 로그인 화면으로 넘어가도 괜찮다고 생각했습니다.

커밋 1: 네트워크 에러 알럿 표시 추가
커밋 2: 구조 변경으로 해당 로직 AppCoordinator로 이동

다른 로직들과는 다르게 이 에러는 프레젠테이션 계층까지 전달되지 않습니다.
특정 화면에서 유저 검증을 시도하는 게 아니라, 앱이 실행될 때 이 로직이 동작하기 때문입니다.
따라서 현재 이 알럿을 표시하는 게 AppCoordinator에서 동작합니다.

로그인/로그아웃과 같은 버튼을 탭한 뒤, 결과가 UI에 늦게 반영되면 로딩 중이라는 표현이 없어 어색한 상황입니다.
로딩 중인 걸 보여줄 화면을 추가하면 어떨까 생각하고 있었는데, 만약 추가한다면 이런 네트워크 에러 알럿도 해당 화면에 붙일 수 있을 거라고 예상하고 있습니다!
해당 내용 참고 부탁드립니다.

Comment on lines +143 to +151
private func showLoginError(_ error: AppError) {
let alert = UIAlertController(
title: "로그인 실패",
message: "다시 시도해 주세요.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "확인", style: .default))
present(alert, animated: true)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

MyPageVC.performSignOut()처럼 error.localizedDescription을 활용해서 다른 메시지로 보이게 하는건 어떨까요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

반영했습니다!
참고 커밋

@github-actions github-actions 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.

🤖 AI 코드 리뷰 - 아키텍처 관점

Apple Sign In 인증 흐름 전체를 추가한 PR이다. Domain 계층에 AuthRepository, UserRepository, SignInService 프로토콜과 UseCase를 신설하고, Data 계층에 Firebase 구현체를 배치한 구조는 레이어 분리 방향이 맞다. 다만 SceneDelegate가 직접 DIContainer를 resolve하고 ViewController 조립·화면 전환·자동 로그인 검증까지 담당하여 단일 책임 범위를 초과하고 있다. AppDelegate에 특정 로컬 IP가 하드코딩되어 있어 협업 시 충돌 위험이 있고, FirestoreUserRepository의 Firestore↔Domain 모델 변환 로직이 레포지터리 내부에 직접 위치하여 확장성이 제한된다.

Comment thread WhereAreYou/WhereAreYou/Core/AppDelegate.swift Outdated
Comment thread WhereAreYou/WhereAreYou/Core/SceneDelegate.swift Outdated
Comment thread WhereAreYou/WhereAreYou/Core/SceneDelegate.swift Outdated
Comment thread WhereAreYou/WhereAreYou/Data/FirestoreUserRepository.swift Outdated
Comment thread WhereAreYou/WhereAreYou/Domain/UseCase/SignInUseCase.swift
Comment thread WhereAreYou/WhereAreYou/Presentation/MyPage/MyPageViewController.swift Outdated
@sangYuLv
sangYuLv requested a review from snughnu September 10, 2026 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants