본문 바로가기
PulseBoard

Firebase Google 로그인 튜토리얼을 “실서비스 아키텍처”로 리팩토링하기 (iOS)

by 밤새는 탐험가89 2025. 12. 23.
728x90
SMALL

https://firebase.google.com/docs/auth/ios/google-signin?hl=ko

 

Apple 플랫폼에서 Google 로그인을 사용하여 인증  |  Firebase Authentication

의견 보내기 Apple 플랫폼에서 Google 로그인을 사용하여 인증 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Google 로그인을 앱에 통합하여 사용자가 Google 계

firebase.google.com

 

Firebase 공식 튜토리얼 구조 요약

공식 문서(Google Sign-In SDK 기준)의 핵심 흐름은 다음과 같다.

📌 튜토리얼의 기본 흐름

  1. GIDSignIn.sharedInstance.signIn
  2. Google 사용자 인증 성공
  3. ID Token + Access Token 획득
  4. GoogleAuthProvider.credential(...)
  5. Auth.auth().signIn(with: credential)

 

여기서 가장 중요한 개념 하나

❗ “Google 로그인” ≠ “Firebase 로그인”

이게 진짜 핵심이야.

  • Google 로그인
    • Google이 “이 사용자가 누구인지” 증명
    • 결과물: ID Token, Access Token
  • Firebase 로그인
    • Firebase가 “이 Google 사용자를 우리 앱 사용자로 받아들임”
    • 입력값: AuthCredential

👉 GoogleAuthProvider.credential(...)
이 메서드는 두 세계를 연결하는 어댑터 역할이야.

 

 

📌 특징

  • ViewController 안에서 모든 로직 처리
  • 로그인 UI / 토큰 처리 / Firebase 연동이 한 파일에 결합
  • “동작 예제”로는 충분하지만
    • 테스트
    • 확장
    • 유지보수
      에는 한계가 있음

 

 

1️⃣ 튜토리얼 코드 ① URL Scheme 처리

📌 Firebase 튜토리얼 코드

func scene(
  _ scene: UIScene,
  openURLContexts URLContexts: Set<UIOpenURLContext>
) {
  return GIDSignIn.sharedInstance.handle(url)
}

 

🔍 이 코드의 역할

  • Google 로그인은 외부 브라우저 / Google 앱을 사용
  • 인증이 끝나면 앱으로 다시 돌아오며 URL 콜백 발생
  • 해당 URL을 Google Sign-In SDK에게 전달해야 로그인 완료

⚠️ 중요한 점

  • 이 코드는 “로그인 로직”이 아님
  • 앱 전역 설정 레벨(SceneDelegate) 책임

👉 따라서 이 부분은 리팩토링 대상이 아님
👉 GoogleAuthHandler로 옮기지 않는다

 

2️⃣ 튜토리얼 코드 ② Client ID 설정

📌 Firebase 튜토리얼 코드

guard let clientID = FirebaseApp.app()?.options.clientID else { return }

let config = GIDConfiguration(clientID: clientID)
GIDSignIn.sharedInstance.configuration = config

 

🔍 이 코드의 역할

  • Firebase 프로젝트에 연결된 Google OAuth Client ID 획득
  • Google Sign-In SDK에 인증 정보 주입

⚠️ 최신 SDK에서의 변화 

  • 최신 GoogleSignIn SDK에서는
    👉 configuration을 명시적으로 설정하지 않아도 됨
  • GoogleService-Info.plist를 통해 자동 설정

 

✅ 우리가 한 선택

// 최신 SDK는 configuration 설정 불필요
GIDSignIn.sharedInstance.signIn(withPresenting: ...)

 

👉 튜토리얼 코드 중 “환경 설정” 부분은 제거

 

3️⃣ 튜토리얼 코드 ③ Google 로그인 시작 

📌 Firebase 튜토리얼 코드

GIDSignIn.sharedInstance.signIn(withPresenting: viewController) { result, error in

 

🔍 이 코드의 역할

  • Google 로그인 UI 표시
  • 사용자가 계정 선택 / 인증 진행

🧠 문제점

  • 보통 이 코드는 ViewController 내부에 위치
  • UI + Auth 로직이 결합됨

 

4️⃣ 튜토리얼 코드 ④ Token 추출

📌 Firebase 튜토리얼 코드

guard let user = result?.user,
      let idToken = user.idToken?.tokenString
else {
  return
}

 

🔍 이 코드의 역할

  • Google 인증 성공 후
    • ID Token
    • Access Token
      획득

👉 Firebase Auth로 넘어가기 위한 재료

 

5️⃣ 튜토리얼 코드 ⑤ Firebase Auth 연동

📌 Firebase 튜토리얼 코드

let credential = GoogleAuthProvider.credential(
  withIDToken: idToken,
  accessToken: user.accessToken.tokenString
)

self.signIn(with: credential)

 

🔍 이 코드의 역할

  • Google 인증 정보 → Firebase 인증 정보로 변환
  • Firebase Auth에 로그인 요청

 

🔄 이제, 이 코드를 어떻게 리팩토링했는가?

6️⃣ 책임 분리의 시작: GoogleAuthHandler

🎯 리팩토링 기준

“Google 로그인 전체 흐름을 하나의 객체가 책임진다”

 

 

 📦 튜토리얼 → GoogleAuthHandler 대응 관계

튜토리얼 위치 역할 리팩토링 후
SceneDelegate URL 콜백 처리 유지 (전역 설정)
ViewController signIn 호출 GoogleAuthHandler
ViewController 토큰 처리 GoogleAuthHandler
ViewController Firebase 연동 GoogleAuthHandler

 

7️⃣ GoogleAuthHandler 구현 핵심

// MARK: - GoogleAuthHandler
/// Google 로그인 전담 핸들러
///
/// Google Sign-In SDK를 통해 인증을 수행하고,
/// 획득한 인증 정보를 Firebase Auth와 연동하는 책임을 가집니다.
final class GoogleAuthHandler {

    // MARK: - Login

    /// Google 로그인 플로우를 시작합니다.
    ///
    /// - Parameters:
    ///   - presentingViewController: Google 로그인 UI를 표시할 ViewController
    ///   - completion: 로그인 성공 / 실패 결과
    func startLogin(
        presentingViewController: UIViewController,
        completion: @escaping (Result<Void, Error>) -> Void
    ) {
        // 최신 GoogleSignIn SDK는 configuration을 직접 전달하지 않음
        GIDSignIn.sharedInstance.signIn(
            withPresenting: presentingViewController
        ) { result, error in

            if let error {
                completion(.failure(error))
                return
            }

            guard
                let user = result?.user,
                let idToken = user.idToken?.tokenString
            else {
                completion(.failure(AuthError.invalidCredential))
                return
            }

            let credential = GoogleAuthProvider.credential(
                withIDToken: idToken,
                accessToken: user.accessToken.tokenString
            )

            // Firebase Auth와 연동
            Auth.auth().signIn(with: credential) { _, error in
                if let error {
                    completion(.failure(error))
                } else {
                    completion(.success(()))
                }
            }
        }
    }
}

 

🔍 무엇이 달라졌나?

  • 튜토리얼 로직 100% 동일
  • 단, 위치와 책임만 변경
  • ViewController는:
    • UI 트리거
    • 결과 수신
      만 담당

 

8️⃣ AuthService.swift 

login 함수 내에 case .google 구현

final class AuthService: AuthProviding {
    
    
    // MARK: - Properties
    
    /// Apple 로그인 전담 핸들러
    private let appleHandler = AppleAuthHandler()
    
    /// Google 로그인 전담 핸들러
    private let googleHandler = GoogleAuthHandler()
    
    /// Firebase Auth 상태 리스너 핸들
    private var authStateHandle: AuthStateDidChangeListenerHandle?
    
    
    // MARK: - Auth State
    
    var currentUserUID: String? {
        Auth.auth().currentUser?.uid
    }
    
    
    func observeAuthState(_ handler: @escaping (String?) -> Void) {
        // Firebase Auth 상태 감시
        authStateHandle = Auth.auth().addStateDidChangeListener { _, user in
            let uid = user?.uid
            handler(uid)
        }
    }
    
    
    // MARK: - Login
    
    func login(
        with provider: SocialLoginProvider,
        from presentationContext: ASAuthorizationControllerPresentationContextProviding,
        completion: @escaping (Result<Void, Error>) -> Void
    ) {
        // 어떤 로그인 방식을 쓸지 분기
        // Google, Kakao, Naver 추가할 때, handler 만들어 case 추가
        switch provider {
            
        case .apple:
            appleHandler.startLogin(
                presentationContext: presentationContext,
                completion: completion
            )
        
        // ✅ google 로그인
        case .google:
            guard let viewController = presentationContext as? UIViewController else {
                completion(.failure(AuthError.invalidCredential))
                return
            }
            
            googleHandler.startLogin(
                presentingViewController: viewController,
                completion: completion
            )
            
        case .kakao,
                .naver:
            completion(.failure(AuthError.unsupportedProvider))
        }
    }

 

728x90
LIST