[UIKit] Firebase Chat App: Kakao Sign In & Sign Out

Junyoung Park·2022년 9월 17일
0

UIKit

목록 보기
36/142
post-thumbnail
post-custom-banner

Kakao Login with iOS

Firebase Chat App: Kakao Sign In & Sign Out

구현 목표

  • 카카오톡 간편 로그인 구현

구현 태스크

  1. 카카오톡 개발자 API 등록: 앱 번들 아이디 및 URL 정보 등록
  2. 카카오톡 로그인 UI
  3. 카카오톡 API 호출
  4. 카카오톡 로그인 토큰 → 토큰 생성 → 유저 정보 패치
  5. 파이어베이스 이메일 등록 및 로그인

핵심 코드

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        if let url = URLContexts.first?.url {
            if (AuthApi.isKakaoTalkLoginUrl(url)) {
                _ = AuthController.handleOpenUrl(url: url)
            }
            ApplicationDelegate.shared.application(
                UIApplication.shared,
                open: url,
                sourceApplication: nil,
                annotation: [UIApplication.OpenURLOptionsKey.annotation]
            )
        }
    }
  • API가 호출하는 URL을 핸들링하기 위한 sceneDelegate 내 함수 구현
    private func isKakaoTalkTokenValid() {
        if AuthApi.hasToken() {
            UserApi.shared.accessTokenInfo { _, error in
                if let error = error {
                    if let sdkError = error as? SdkError, sdkError.isInvalidTokenError() == true {
                        // sign in
                        print("NO TOKEN HERE 1")
                        self.kakaoTalkSignIn()
                    } else {
                        print(error.localizedDescription)
                    }
                } else {
                    self.getUserFromKakaoTalk()
                }
            }
        } else {
            print("NO TOKEN HERE 2")
            kakaoTalkSignIn()
        }
    }
  • 현재 카카오톡 로그인에 사용 가능한 유효 토큰을 확인하는 함수
  • 토큰이 사용가능하다면 곧바로 유저 정보 패치
  • 토큰 갱신 필요 또는 토큰이 없을 때 로그인 요청
    private func kakaoTalkSignIn() {
        var accessToken: String = ""
        if UserApi.isKakaoTalkLoginAvailable() {
            UserApi.shared.loginWithKakaoTalk { oAuthToken, error in
                guard
                    let token = oAuthToken,
                    error == nil else {
                    if let error = error {
                        print(error.localizedDescription)
                    }
                    return
                }
                accessToken = token.accessToken
                self.getUserFromKakaoTalk()
            }
        } else {
            UserApi.shared.loginWithKakaoAccount { oAuthAccount, error in
                guard
                    let account = oAuthAccount,
                    error == nil else {
                    if let error = error {
                        print(error.localizedDescription)
                    }
                    return
                }
                accessToken = account.accessToken
                print("TOKEN GET FROM LOGIN")
                self.getUserFromKakaoTalk()
            }
        }
    }
  • 디바이스 내 카카오톡 어플 설치 여부에 따라 어플/웹 이용 가능
  • 액세스 토큰을 통해 현재 사용 가능한 토큰을 패치
private func getUserFromKakaoTalk() {
        UserApi.shared.me() { user, error in
            guard
                let user = user,
                error == nil else {
                if let error = error {
                    print(error.localizedDescription)
                }
                return
            }
            var scopes = [String]()
            if user.kakaoAccount?.profileNicknameNeedsAgreement == true {
                scopes.append("profile_nickname")
            }
            if user.kakaoAccount?.emailNeedsAgreement == true {
                scopes.append("account_email")
            }
            
            if !scopes.isEmpty {
                print("SCORES ARE INCLUDED")
                UserApi.shared.loginWithKakaoAccount(scopes: scopes) { (_, error) in
                    if let error = error {
                        print(error.localizedDescription)
                    } else {
                        UserApi.shared.me() { user, error in
                            if let error = error {
                                print(error.localizedDescription)
                            } else {
                                let firstName = user?.kakaoAccount?.name ?? ""
                                let lastName = user?.kakaoAccount?.profile?.nickname ?? ""
                                var email = ""
                                if let kakaoEmail = user?.kakaoAccount?.email {
                                    email = kakaoEmail
                                } else if let kakaoPhoneNumber = user?.kakaoAccount?.phoneNumber {
                                    email = kakaoPhoneNumber
                                }
                                let password = "\(user?.id ?? 12345678)"
                                self.kakaoToFirebaseAuth(firstName: firstName, lastName: lastName, email: email, password: password)
                            }
                        }
                    }
                }
            } else {
                print("There is no need to get user agreements")
                let firstName = user.kakaoAccount?.name ?? ""
                let lastName = user.kakaoAccount?.profile?.nickname ?? ""
                var email = ""
                if let kakaoEmail = user.kakaoAccount?.email {
                    email = kakaoEmail
                } else if let kakaoPhoneNumber = user.kakaoAccount?.phoneNumber {
                    email = kakaoPhoneNumber
                }
                let password = "\(user.id ?? 12345678)"
                self.kakaoToFirebaseAuth(firstName: firstName, lastName: lastName, email: email, password: password)
            }
        }
    }
  • 닉네임, 이메일 등 유저의 허락이 필요한 사용자 정보 패치
  • 허락을 받지 못했을 때 scopes에 필요한 유저 정보를 등록, 새롭게 허락을 구하기
  • 허락을 미리 받았다면 패치한 유저 정보를 바탕으로 파이어베이스 로그인
    private func kakaoToFirebaseAuth(firstName: String, lastName: String, email: String, password: String) {
        DatabaseManager.shared.userExists(with: email, at: .Kakao) { exists in
            if !exists {
                print("THERE IS NO FIREBASE KAKAO USER")
                DatabaseManager.shared.insertUser(with: ChatAppUser(firstName: firstName, lastName: lastName, emailAddress: email, platform: .Kakao))
                FirebaseAuth.Auth.auth().createUser(withEmail: Platform.Kakao.rawValue.lowercased() + email, password: password) { result, error in
                    guard
                        let _ = result,
                        error == nil else {
                        if let error = error {
                            print(error.localizedDescription)
                        }
                        return
                    }
                }
            }
        }
        
        FirebaseAuth.Auth.auth().signIn(withEmail: Platform.Kakao.rawValue.lowercased() + email, password: password) { [weak self] result, error in
            guard let self = self else { return }
            guard
                let _ = result,
                error == nil else {
                if let error = error {
                    print(error.localizedDescription)
                }
                return
            }
            print("Successfully Kakao Sign In")
            NotificationCenter.default.post(name: .didSignInNotification, object: nil)
        }
    }
  • 커스텀 토큰이 아닌 기존의 이메일/패스워드 방법으로 파이어베이스 인증을 사용하고 있기 때문에 해당 플랫폼 값을 더한 값을 이메일 주소로 활용함(임시)
  • 파이어베이스 데이터베이스 생성 및 유저 등록, 로그인을 해당 카카오톡 로그인과 함께 해야 하는 tradeoff 존재

구현 화면

  • 파이어베이스에서 카카오톡 인증을 제공하지 않기 때문에 카카오톡 로그인 이외에 별도의 커스텀 토큰 또는 기존의 이메일/패스워드 인증을 사용해야 하는 번거로움이 존재한다.
  • 커스텀 토큰 JWT 사용 방법보다 간단한 기존의 파이어베이스 인증 사용 방법을 적용했지만, 안전성 및 사용의 용이함을 위해서는 (서버 단의 불편함을 차치하고서라도) 커스텀 토큰을 사용하는 게 보다 이득이라 생각된다.
profile
JUST DO IT
post-custom-banner

0개의 댓글