Kakao Login with iOS
Firebase Chat App: Kakao Sign In & Sign Out
구현 목표
구현 태스크
- 카카오톡 개발자 API 등록: 앱 번들 아이디 및 URL 정보 등록
- 카카오톡 로그인 UI
- 카카오톡 API 호출
- 카카오톡 로그인 토큰 → 토큰 생성 → 유저 정보 패치
- 파이어베이스 이메일 등록 및 로그인
핵심 코드
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 {
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
사용 방법보다 간단한 기존의 파이어베이스 인증 사용 방법을 적용했지만, 안전성 및 사용의 용이함을 위해서는 (서버 단의 불편함을 차치하고서라도) 커스텀 토큰을 사용하는 게 보다 이득이라 생각된다.