본문 바로가기
PulseBoard

KakaoAuthHandler는 “무엇을 기준으로 설계했나?”

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

한 문장 요약

Apple / Google 로그인 핸들러와 “동일한 추상화 레벨”을 유지하면서,
Kakao SDK의 특성을 Swift Concurrency에 맞게 감싸는 것

 

이게 설계의 핵심이야.

 

2️⃣ 설계 기준 ① — “Handler 패턴” 유지

이미 네 프로젝트에 존재하는 구조

이미 이런 패턴을 쓰고 있어:

AuthService
 ├─ AppleAuthHandler
 ├─ GoogleAuthHandler
 └─ (KakaoAuthHandler ← 여기에 추가)
 

이 구조의 장점은 명확:

  • AuthService는 로그인 전략을 선택만 함
  • 각 Handler는 자기 SDK만 책임
  • ViewModel은 AuthService만 의존

👉 그래서 Kakao도 무조건 Handler로 들어가야 함

 

3️⃣ 설계 기준 ② — “책임을 accessToken까지로 제한”

KakaoAuthHandler의 책임 범위

KakaoAuthHandler
 └─ Kakao SDK 사용
     └─ 사용자 인증
         └─ accessToken 반환 (여기까지)

 

❌ 하지 않는 것:

  • Firebase 로그인
  • 서버 통신
  • Custom Token 처리

근거 (아키텍처 원칙)

이건 Clean Architecture / SRP(Single Responsibility Principle) 그대로야.

“Kakao SDK가 바뀌면, KakaoAuthHandler만 바뀌어야 한다.”

 

만약 KakaoAuthHandler 안에서:

  • Firebase Functions 호출
  • Firebase Auth 로그인까지 해버리면?

👉 Kakao SDK 변경 + Firebase 변경이 한 클래스에 섞임

이건 실무에서 바로 리젝 사유야.

 

4️⃣ 설계 기준 ③ — async/await로 통일

Kakao SDK의 원래 API

Kakao SDK는 콜백 기반이야:

UserApi.shared.loginWithKakaoTalk { oauthToken, error in
    ...
}
 

하지만 프로젝트는 이미:

  • Swift Concurrency 사용
  • Apple / Google 로그인도 async 기반

그래서 우리는 이걸 이렇게 감쌌지:

try await withCheckedThrowingContinuation { continuation in
    ...
}

 

이 패턴의 근거 자료

🔗 Apple 공식 문서

Apple이 직접 권장하는 방식

“기존 callback API를 async/await로 감싸라”

 

5️⃣ 설계 기준 ④ — “카카오톡 / 계정 로그인 분기”

Kakao 공식 가이드의 핵심

Kakao는 공식적으로 말해:

“카카오톡이 설치되어 있으면 카카오톡 로그인 사용을 권장”

 

그래서 SDK도 아예 이 API를 제공해:

UserApi.isKakaoTalkLoginAvailable()

 

이걸 무시하고:

  • 항상 account 로그인만 쓰거나
  • 항상 talk 로그인만 쓰는 건
    👉 공식 가이드 위반

그래서 login()의 흐름은 이렇게 설계됨:

func login() async throws -> String {
    if UserApi.isKakaoTalkLoginAvailable() {
        return try await loginWithKakaoTalk()
    } else {
        return try await loginWithKakaoAccount()
    }
}

 

📌 이 구조는 카카오 공식 샘플 코드의 구조를 그대로 유지하면서
Swift Concurrency만 얹은 형태야.

 

6️⃣ “근거 자료” 정리 (네가 요청한 부분)

아래는 실제로 참고 가능한 근거들이야.

① Kakao iOS SDK 공식 문서

  • Kakao iOS SDK – Login
  • UserApi.shared.loginWithKakaoTalk
  • UserApi.shared.loginWithKakaoAccount

👉 우리가 쓴 API는 전부 공식 문서에 있는 것

https://developers.kakao.com/docs/latest/ko/kakaologin/ios

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해 보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

② Apple 공식 Swift Concurrency 문서

👉 callback → async 변환 방식의 정석

 

③ Firebase / Google / Apple 로그인과의 “레벨 맞추기”

이건 특정 문서라기보다 실무 관례야.

  • AppleAuthHandler → 인증 토큰 반환
  • GoogleAuthHandler → 인증 토큰 반환
  • KakaoAuthHandler → accessToken 반환

👉 “소셜 로그인 Handler는 토큰까지만”
이건 실무에서 거의 암묵적인 룰

 

import Foundation


// MARK: - KakaoAuthProviding
/// Kakao 로그인을 담당하는 인터페이스입니다.
///
/// AuthService는 이 Protocol에만 의존하며,
/// Kakao SDK의 실제 구현 디테일은 알 필요가 없습니다.
protocol KakaoAuthProviding {
    
    /// 카카오 로그인을 수행합니다.
    ///
    /// - Returns: Kakao accessToken
    /// - Throws: 로그인 실패 시 에러
    func login() async throws -> String
}



----------------------------------------------------------------

import Foundation
import KakaoSDKAuth
import KakaoSDKUser


// MARK: - KakaoAuthHandler

/// Kakao SDK를 사용해 실제 로그인 로직을 수행하는 구현체입니다.
///
/// 책임:
/// 1. 카카오톡 설치 여부 판단
/// 2. 카카오톡 / 카카오계정 로그인 분기
/// 3. 로그인 성공 시 accessToken 반환
///
/// ❗️Firebase, 서버 통신(Custom Token)은 여기서 다루지 않습니다.
final class KakaoAuthHandler: KakaoAuthProviding {
    
    // MARK: - Public
    
    /// Kakao 로그인 진입점
    ///
    /// 카카오톡 설치 여부에 따라
    /// - 설치 O → 카카오톡 로그인
    /// - 설치 X → 카카오 계정 로그인
    func login() async throws -> String {
        if UserApi.isKakaoTalkLoginAvailable() {
            return try await loginWithKakaoTalk()
        } else {
            return try await loginWithKakaoAccount()
        }
    }
}



// MARK: - Private Login Methods

private extension KakaoAuthHandler {
    
    /// 카카오톡 로그인
    ///
    /// Kakao SDK의 callback 기반 API를
    /// Swift async/await 스타일로 래핑합니다.
    func loginWithKakaoTalk() async throws -> String {
        try await withCheckedThrowingContinuation { continuation in
            UserApi.shared.loginWithKakaoTalk { oauthToken, error in
                
                // 1️⃣ Kakao SDK 에러 발생
                if let error = error {
                    continuation.resume(throwing: error)
                    return
                }
                
                // 2️⃣ accessToken 추출 실패
                guard let accessToken = oauthToken?.accessToken else {
                    continuation.resume(throwing: KakaoAuthError.failedToGetToken)
                    return
                }
                
                // 3️⃣ 로그인 성공 → accessToken 반환
                continuation.resume(returning: accessToken)
            }
        }
    }
    
    
    /// 카카오 계정 로그인 (웹 로그인)
    ///
    /// 카카오톡이 설치되어 있지 않은 경우 사용됩니다.
    func loginWithKakaoAccount() async throws -> String {
        try await withCheckedThrowingContinuation { continuation in
            UserApi.shared.loginWithKakaoAccount { oauthToken, error in
                
                // 1️⃣ Kakao SDK 에러 발생
                if let error = error {
                    continuation.resume(throwing: error)
                    return
                }
                
                // 2️⃣ accessToken 추출 실패
                guard let accessToken = oauthToken?.accessToken else {
                    continuation.resume(throwing: KakaoAuthError.failedToGetToken)
                    return
                }
                
                // 3️⃣ 로그인 성공 → accessToken 반환
                continuation.resume(returning: accessToken)
            }
        }
    }
}



// MARK: - KakaoAuthError
/// Kakao 로그인 과정에서 발생할 수 있는 에러 정의
enum KakaoAuthError: Error {
    
    /// accessToken을 정상적으로 얻지 못한 경우
    case failedToGetToken
}
728x90
LIST