카카오 로그인 - KakaoSDKCommon.ClientFailureReason.TokenNotFound

문의 사항에 따라 필요한 정보를 먼저 입력하시면 더 빠르게 대응해 드릴 수 있습니다.

  • 개발 과정에서 문제가 있을 경우
    • 앱 아이디(app ID):ID 1002438O
    • 호스팅 사: 없음(ios / android만)
    • 서비스 URL : 없음
    • 오류 내용 : [:bangbang:][Api.swift 151:29] → response:
      api error: ClientFailed(reason: KakaoSDKCommon.ClientFailureReason.TokenNotFound, errorMessage: Optional(“authentication tokens not exist.”)) 라고 계속 뜹니다.

Appdelegate.swift 를 MainView.swift 에 넣고 kakaoInit을 했고,
웹은 로그인이 되는것 같은데 앱으로 로그인 시에 문제가 생깁니다.


스크린샷 2024-01-08 오후 7.10.03

아래는 로그입니다.

2024-01-08 07:03:23294 [💬][AuthApiCommon.swift 49:17] -> >>>> [KakaoSDKCommon.SessionType.RxAuthApi: Alamofire.Session, KakaoSDKCommon.SessionType.Api: Alamofire.Session, KakaoSDKCommon.SessionType.AuthApi: Alamofire.Session, KakaoSDKCommon.SessionType.Auth: Alamofire.Session]

2024-01-08 07:03:23296 [💬][AuthController.swift 415:21] -> code_verifier: plqYwR/UicgV+a6Abx76pNhW5/k6r20okGXnROT28y57b4IIQSOJWvj4J747C5F2siZ4pmz2IslHbMx58wXQ0g

2024-01-08 07:03:23296 [💬][AuthController.swift 418:25] -> code_challenge: sK4gyNjbHLIJU63loCdjvP2nCtqzGwu3oXfPjCKV8kI

2024-01-08 07:03:23400 [ℹ️][AuthController.swift 145:25] -> 카카오톡 실행: https://talk-apps.kakao.com/scheme/kakaokompassauth%3A%2F%2Fauthorize%3Fclient_id=20741c1f4d3ee4183acd95f528719b99&headers=%257B%2522KA%2522%3A%2522sdk%255C%2F2.20.0%2520sdk_type%255C%2Fswift%2520os%255C%2Fios-17.1.2%2520lang%255C%2Fko-KR%2520res%255C%2F375x667%2520device%255C%2FiPhone%2520origin%255C%2Fkr.yagiyagi.ios.camp%2520app_ver%255C%2F1.3%2522%257D&redirect_uri=kakao20741c1f4d3ee4183acd95f528719b99%3A%2F%2Foauth&response_type=code&deep_link_method=universal_link&params=%257B%2522code_challenge_method%2522%3A%2522S256%2522,%2522code_challenge%2522%3A%2522sK4gyNjbHLIJU63loCdjvP2nCtqzGwu3oXfPjCKV8kI%2522%257D

Snapshot request 0x28128efd0 complete with error: <NSError: 0x2812a73c0; domain: BSActionErrorDomain; code: 6 ("anulled")>

2024-01-08 07:03:24625 [‼️][AuthRequestRetrier.swift 40:25] -> request retrier:

error:requestAdaptationFailed(error: KakaoSDKCommon.SdkError.ClientFailed(reason: KakaoSDKCommon.ClientFailureReason.TokenNotFound, errorMessage: Optional("authentication tokens not exist.")))

not api error -> pass through

2024-01-08 07:03:24626 [‼️][AuthRequestRetrier.swift 119:21] -> request retrier:

not handled error -> pass through

2024-01-08 07:03:24628 [‼️][Api.swift 151:29] -> response:

api error: ClientFailed(reason: KakaoSDKCommon.ClientFailureReason.TokenNotFound, errorMessage: Optional("authentication tokens not exist."))

  • 퍼머링크 생성이 필요할 경우 (자세한 신청 방법은 가이드 참고)
    • 카카오싱크 퍼머링크의 서비스 랜딩URL:
    • 디벨로퍼스 앱과 연결된 카카오톡 채널의 검색용 아이디:

카카오 로그인 관련 에러(Invalid redirect. 예: KOE006)가 발생할 경우, 가이드를 참고합니다.
카카오 싱크 관련 자주하는 질문은 FAQ를 참고합니다.

안녕하세요.

아래 가이드 구현 확인 부탁드립니다.

iOS | Kakao Developers iOS - 카카오톡으로 로그인을 위한 설정

네 가이드대로 여러번 이미 모두 체크해보았으나 문제가 없습니다

안녕하세요.

카카오톡 실행 후, 앱으로 돌아왔을 때 이후 처리가 없는 것으로 보이는데요
AppDelegate 또는 SceneDelegate 구현하신 코드 다시 확인 부탁드리며

아래 제공되는 샘플코드와 구성 내용을 비교해 보시면 좋을것 같습니다.

다운로드 | Kakao Developers 다운로드

앱으로 실행과 동의화면까지는 잘 가고 이후 액션이 없다고 하셨는데 카카오가 깔려있지 않아서 이메일, 비밀번호를 칠때는 로그인이 잘됩니다.

아래는 KakaoSignInButton 버튼 소스코드입니다.

//
//  KakaoSignInButton.swift
//  camp
//
//  Created by minsekim on 12/1/23.
//

import Foundation
import SwiftUI
import KakaoSDKCommon
import KakaoSDKAuth
import KakaoSDKUser
import AuthenticationServices
import Amplitude


struct KakaoSignInButton: View {
    @ObservedObject var appState = AppState.shared
    

    
    var body: some View {
        Button(action: kakaoLogin) {
            HStack {
                Image("KakaoLogin") // 카카오 로고 이미지
                    .resizable()
                    .scaledToFit()
                    .frame(width: 20, height: 20)
                Text("카카오로 3초만에 시작하기")
                    .foregroundColor(.black)
                    .font(.custom("Pretendard", size: 15))
            }
            .padding(.leading, 40)
            .padding(.trailing, 40)
            .padding()
            .background(Color(hex: "FFE94E"))
            .cornerRadius(10)
        }
    }
    func fetchMarketingAgreement(_ accessToken: String?, completion: @escaping (Bool) -> Void) {
        guard let token = accessToken else {
               print("AccessToken is not available")
               return
           }

        let url = URL(string: "https://kapi.kakao.com/v1/user/service/terms")!
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

        print("Starting fetchMarketingAgreement request")

        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            guard error == nil, let data = data else {
                print("Error: \(error?.localizedDescription ?? "Unknown error")")
                return
            }

            do {
                if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
                   let terms = jsonResult["allowed_service_terms"] as? [[String: Any]] {
                    let isMktAgree = terms.contains { $0["tag"] as? String == "mkt_agree" }
                    completion(isMktAgree)
                }
            } catch {
                print("JSON 파싱 에러: \(error)")
            }
            print("fetchMarketingAgreement request completed")
          }
          task.resume()
      }
    func kakaoLogin() {
        if UserApi.isKakaoTalkLoginAvailable() {
            UserApi.shared.loginWithKakaoTalk { (oauthToken, error) in
                if let error = error {
                    print("1 로그인 실패: \(error)")
                } else if let token = oauthToken {
                    print("AccessToken: \(token.accessToken)") // 로그 추가
                    self.getUserInfo(accessToken: token.accessToken)
                } else {
                    print("1 로그인 실패(No token): \(String(describing: error))")
                }
            }
        } else {
            UserApi.shared.loginWithKakaoAccount { (oauthToken, error) in
                if let error = error {
                    print("2 로그인 실패: \(error)")
                } else if let token = oauthToken {
                    print("AccessToken: \(token.accessToken)") // 로그 추가
                    self.getUserInfo(accessToken: token.accessToken)
                } else {
                    print("2 로그인 실패(No token): \(String(describing: error))")
                }
            }
        }
    }
    

    func getUserInfo(accessToken: String?) {
        
        UserApi.shared.me() { (user, error) in
            if let error = error {
            
                        print("사용자 정보 요청 실패: \(error)")
            } else if let user = user {
            
                // 옵셔널 바인딩을 사용하여 user.id의 nil 체크를 합니다.
                if let kakaoID = user.id {
                    
                    let birthdate = "\(user.kakaoAccount?.birthyear ?? "0000")\(user.kakaoAccount?.birthday ?? "0000")"
                    sendUserInfoToServer(
                        kakaoID: kakaoID,
                        name: user.kakaoAccount?.name,
                        birthdate: birthdate,
                        phone: user.kakaoAccount?.phoneNumber,
                        email: user.kakaoAccount?.email,
                        gender: user.kakaoAccount?.gender?.rawValue == "male" ? "MALE" : "FEMALE",
                        profileImage: user.kakaoAccount?.profile?.profileImageUrl?.absoluteString,
                        accessToken: accessToken
                    )
                    
                } else {
                    print("카카오 사용자 ID가 존재하지 않습니다.")
                }
                
            }
            
        }
    }
    
    func sendUserInfoToServer(kakaoID: Int64, name: String?, birthdate: String, phone: String?, email: String?, gender: String?, profileImage: String?, accessToken: String?) {
        print(-2)
        fetchMarketingAgreement(accessToken) { isMktAgree in
            print(-1)
            let url = URL(string: "https://node5.yagiyagi.kr/user/kakao")!
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            request.setValue("Bearer \(AuthorizationKeyGenerator.generate())", forHTTPHeaderField: "Authorization") // 인증 키를 헤더에 추가
            
            let body: [String: Any?] = [
                "kakao_id": kakaoID,
                "name": name,
                "birthdate": birthdate,
                "phone": phone,
                "email": email,
                "gender": gender,
                "profile_image": profileImage,
                "is_mkt_agree":isMktAgree
            ]
            
            print(0)
            request.httpBody = try? JSONSerialization.data(withJSONObject: body.compactMapValues { $0 }, options: [])
            print(1)
            URLSession.shared.dataTask(with: request) { data, response, error in
                if let error = error {
                    print("Error: \(error)")
                    return
                }
                print(2)
                
                if let data = data {
                    do {
                        print(3)
                        if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
                           let dataResponse = jsonResponse["data"] as? [String: Any],
                           let userID = dataResponse["user_id"] as? Int {
                            
                            
                            print(4)
                            DispatchQueue.main.async {
                                // UserDefaults에 user_id 저장
                                UserDefaults.standard.set(userID, forKey: "userId")
                                print(5)
                                print("회원가입/로그인 성공, User ID: \(userID) 저장됨")
                                appState.isUserLoggedIn = true
                                Amplitude.instance().setUserId(String("\(userID)"))
                                // 화면 전환 로직
                                // 먼저 웹 뷰 스택을 비운다
                                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                                    appState.stackedWebViews = []
                                }
                                
                                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                                    appState.stackedWebViews = [AppState.BASE_URL]
                                }
                                
                                
                                appState.isLoading = true
                                saveFcmToken()
                                
                                // 예를 들어 웹뷰 화면으로 이동하는 로직 추가
                            }
                        } else {
                            print("서버 응답 파싱 실패")
                        }
                    } catch {
                        print("JSON 파싱 에러: \(error)")
                    }
                }
            }.resume()
            
        }
    }
}

extension Color {
    init(hex: String) {
        let scanner = Scanner(string: hex)
        _ = scanner.scanString("#")

        var rgb: UInt64 = 0
        scanner.scanHexInt64(&rgb)

        let r = Double((rgb >> 16) & 0xFF) / 255.0
        let g = Double((rgb >> 8) & 0xFF) / 255.0
        let b = Double(rgb & 0xFF) / 255.0

        self.init(red: r, green: g, blue: b)
    }
}


아래는 시작 view입니다.
MainView.swift

@main
struct CampApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

    init() {
        // Kakao SDK 초기화
        KakaoSDK.initSDK(appKey: "207...99")
    }

    var body: some Scene {
        WindowGroup {
            SplashScreenView()
        }
    }
}

아래는 AppDelegate.swift입니다.

//
//  AppDelegate.swift
//  camp
//
//  Created by minsekim on 12/26/23.
//

import Foundation

import SwiftUI
import FirebaseCore
import FirebaseMessaging
import UserNotifications
import Amplitude


class AppDelegate: NSObject, UIApplicationDelegate {
    @ObservedObject var appState = AppState.shared
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {

        let userInfo = response.notification.request.content.userInfo

        // 'link' 키의 URL을 처리
        if let link = userInfo["link"] as? String {
            nativePush(path: link)
        }

        completionHandler()
    }

    private func nativePush(path: String) {
           // AppState 인스턴스에 접근하기 위해 수정된 부분
           

           if var urlComponents = URLComponents(url: AppState.BASE_URL, resolvingAgainstBaseURL: true) {
               // path는 옵셔널이 아니므로 직접 접근 가능
               let slicePath = path.dropFirst()
               urlComponents.path += slicePath
               print("urlComponents.path + url: "+String("\(urlComponents.path)"))

               if let finalUrl = urlComponents.url {
                   DispatchQueue.main.async {
                       self.appState.stackedWebViews.append(finalUrl)
                   }
               }
           }
       }

    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        Amplitude.instance().initializeApiKey("19...8d")
        Amplitude.instance().defaultTracking.sessions = true
        
        
        let userId = UserDefaults.standard.string(forKey: "userId") ?? "null"
        if(userId != "null"){
            Amplitude.instance().setUserId(userId)
        }
        
        // 파이어베이스 설정
        FirebaseApp.configure()
        
        // 앱 실행 시 사용자에게 알림 허용 권한을 받음
        UNUserNotificationCenter.current().delegate = self
        
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] // 필요한 알림 권한을 설정
        UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: { _, _ in }
        )
        
        // UNUserNotificationCenterDelegate를 구현한 메서드를 실행시킴
        application.registerForRemoteNotifications()
        
        // 파이어베이스 Meesaging 설정
        Messaging.messaging().delegate = self
        
        return true
    }
    //앱이 화면에 켜져있는 상태에서도 push notification을 받고 싶다면 아래 메서드를 꼭 추가해주세요!
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.list, .banner])
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {
    
    // 백그라운드에서 푸시 알림을 탭했을 때 실행
    func application(_ application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("APNS token: \(deviceToken)")
        Messaging.messaging().apnsToken = deviceToken
    }
    
    // Foreground(앱 켜진 상태)에서도 알림 오는 설정
//    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
//        completionHandler([.list, .banner])
//    }
}

extension AppDelegate: MessagingDelegate {
    
    // 파이어베이스 MessagingDelegate 설정
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
      print("Firebase registration token: \(String(describing: fcmToken))")

      let dataDict: [String: String] = ["token": fcmToken ?? ""]
        
        saveFcmToken()
        
      NotificationCenter.default.post(
        name: Notification.Name("FCMToken"),
        object: nil,
        userInfo: dataDict
      )
      // TODO: If necessary send token to application server.
      // Note: This callback is fired at each app startup and whenever a new token is generated.
    }
}




func saveFcmToken() {
    print("saveFcmToken")

    Messaging.messaging().token { token, error in
        if let error = error {
            print("Error fetching FCM token: \(error)")
        } else if let token = token {
            print("FCM Token: \(token)")
            // 서버에 토큰 전송
            guard let userId = UserDefaults.standard.string(forKey: "userId") else {
                print("User ID not found")
                sendTokenToServer(userId: "0", token: token)
                return
            }
            sendTokenToServer(userId: userId, token: token)
        }
    }
}


func sendTokenToServer(userId: String, token: String) {
    print("sendTokenToServer")
    
    let url = URL(string: "백엔드주소")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("Bearer \(AuthorizationKeyGenerator.generate())", forHTTPHeaderField: "Authorization") // 인증 키를 헤더에 추가
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")

    let body: [String: Any] = ["user_id": userId, "token": token]
    request.httpBody = try? JSONSerialization.data(withJSONObject: body)

    URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            print("Error sending token to server: \(error)")
            return
        }
        if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
            print("Token successfully sent to server")
        } else {
            print("Failed to send token to server")
        }
    }.resume()
}

추가로,

1.api error: ClientFailed(reason: KakaoSDKCommon.ClientFailureReason.TokenNotFound, errorMessage: Optional(“authentication tokens not exist.”))

해당 에러도 앱으로 돌아왔을때 처리가 없을때랑 관련있는것인지 확인 부탁드려요.

  1. 백엔드주소와 native key는 임시로 … 으로 대체하였습니다.
  2. KakaoSignInButton 에서 func kakaoLogin 부분을 살펴봐주세요.

혹시나 싶어 AppDelegate에 kakao init 을 넣고 AuthApi.isKakaoTalkLoginUrl 도 넣어 보았는데 그대로 작동하지 않네요.

아래는 수정된
AppDelegate.swfit 입니다.

//
//  AppDelegate.swift
//  camp
//
//  Created by minsekim on 12/26/23.
//

import Foundation

import SwiftUI
import FirebaseCore
import FirebaseMessaging
import UserNotifications
import Amplitude
import KakaoSDKCommon
import KakaoSDKAuth


class AppDelegate: NSObject, UIApplicationDelegate {
    @ObservedObject var appState = AppState.shared
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {

        let userInfo = response.notification.request.content.userInfo

        // 'link' 키의 URL을 처리
        if let link = userInfo["link"] as? String {
            nativePush(path: link)
        }

        completionHandler()
    }

    private func nativePush(path: String) {
           // AppState 인스턴스에 접근하기 위해 수정된 부분
           

           if var urlComponents = URLComponents(url: AppState.BASE_URL, resolvingAgainstBaseURL: true) {
               // path는 옵셔널이 아니므로 직접 접근 가능
               let slicePath = path.dropFirst()
               urlComponents.path += slicePath
               print("urlComponents.path + url: "+String("\(urlComponents.path)"))

               if let finalUrl = urlComponents.url {
                   DispatchQueue.main.async {
                       self.appState.stackedWebViews.append(finalUrl)
                   }
               }
           }
       }

    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        Amplitude.instance().initializeApiKey("19...8d")
        Amplitude.instance().defaultTracking.sessions = true
        
        
        let userId = UserDefaults.standard.string(forKey: "userId") ?? "null"
        if(userId != "null"){
            Amplitude.instance().setUserId(userId)
        }
        
        // 파이어베이스 설정
        FirebaseApp.configure()
        
        // 앱 실행 시 사용자에게 알림 허용 권한을 받음
        UNUserNotificationCenter.current().delegate = self
        
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] // 필요한 알림 권한을 설정
        UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: { _, _ in }
        )
        
        // UNUserNotificationCenterDelegate를 구현한 메서드를 실행시킴
        application.registerForRemoteNotifications()
        
        // 파이어베이스 Meesaging 설정
        Messaging.messaging().delegate = self
        
        // Kakao SDK 초기화
        KakaoSDK.initSDK(appKey: "20741c1f4d3ee4183acd95f528719b99")
        
        return true
    }
    
    // 카카오 로그인 처리를 위한 URL 열기 메서드 추가
     func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
         if AuthApi.isKakaoTalkLoginUrl(url) {
             return AuthController.handleOpenUrl(url: url)
         }
         // 다른 URL 처리를 위한 코드 (필요한 경우)
         return false
     }
    
    //앱이 화면에 켜져있는 상태에서도 push notification을 받고 싶다면 아래 메서드를 꼭 추가해주세요!
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.list, .banner])
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {
    
    // 백그라운드에서 푸시 알림을 탭했을 때 실행
    func application(_ application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("APNS token: \(deviceToken)")
        Messaging.messaging().apnsToken = deviceToken
    }
    
    // Foreground(앱 켜진 상태)에서도 알림 오는 설정
//    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
//        completionHandler([.list, .banner])
//    }
}

extension AppDelegate: MessagingDelegate {
    
    // 파이어베이스 MessagingDelegate 설정
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
      print("Firebase registration token: \(String(describing: fcmToken))")

      let dataDict: [String: String] = ["token": fcmToken ?? ""]
        
        saveFcmToken()
        
      NotificationCenter.default.post(
        name: Notification.Name("FCMToken"),
        object: nil,
        userInfo: dataDict
      )
      // TODO: If necessary send token to application server.
      // Note: This callback is fired at each app startup and whenever a new token is generated.
    }
}




func saveFcmToken() {
    print("saveFcmToken")

    Messaging.messaging().token { token, error in
        if let error = error {
            print("Error fetching FCM token: \(error)")
        } else if let token = token {
            print("FCM Token: \(token)")
            // 서버에 토큰 전송
            guard let userId = UserDefaults.standard.string(forKey: "userId") else {
                print("User ID not found")
                sendTokenToServer(userId: "0", token: token)
                return
            }
            sendTokenToServer(userId: userId, token: token)
        }
    }
}


func sendTokenToServer(userId: String, token: String) {
    print("sendTokenToServer")
    
    let url = URL(string: "https://node5.yagiyagi.kr/user/token")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("Bearer \(AuthorizationKeyGenerator.generate())", forHTTPHeaderField: "Authorization") // 인증 키를 헤더에 추가
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")

    let body: [String: Any] = ["user_id": userId, "token": token]
    request.httpBody = try? JSONSerialization.data(withJSONObject: body)

    URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            print("Error sending token to server: \(error)")
            return
        }
        if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
            print("Token successfully sent to server")
        } else {
            print("Failed to send token to server")
        }
    }.resume()
}

카카오톡이 설치되어 있지 않은데 if UserApi.isKakaoTalkLoginAvailable() 구문을 통과하고 UserApi.shared.loginWithKakaoTalk 기능이 호출되었다는 말씀이신가요?

아니요 카카오가 깔려있지 않을때 실행되는 함수의 로그인은 잘 됩니다.
isKakaoTalkLoginAvailable == false 일때요.

카카오가 깔려있을때는 카카오로그인 후에 화면 상 뒤로가고 나서 가만히 있네요.

AppDelegate가 UIResponder를 상속받도록 해보시겠어요?

아래 부분처럼 바꾸어도 그대로네요…
(class AppDelegate: UIResponder, UIApplicationDelegate {)

class AppDelegate: UIResponder, UIApplicationDelegate {
    @ObservedObject var appState = AppState.shared
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {

        let userInfo = response.notification.request.content.userInfo

        // 'link' 키의 URL을 처리
        if let link = userInfo["link"] as? String {
            nativePush(path: link)
        }

        completionHandler()
    }


    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
 Amplitude.instance().defaultTracking.sessions = true
        
        
        let userId = UserDefaults.standard.string(forKey: "userId") ?? "null"
        if(userId != "null"){
            Amplitude.instance().setUserId(userId)
        }
        
        // 파이어베이스 설정
        FirebaseApp.configure()
        
        // 앱 실행 시 사용자에게 알림 허용 권한을 받음
        UNUserNotificationCenter.current().delegate = self
        
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] // 필요한 알림 권한을 설정
        UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: { _, _ in }
        )
        
        // UNUserNotificationCenterDelegate를 구현한 메서드를 실행시킴
        application.registerForRemoteNotifications()
        
        // 파이어베이스 Meesaging 설정
        Messaging.messaging().delegate = self
        
        // Kakao SDK 초기화
        KakaoSDK.initSDK(appKey: "20741c1f4d3ee4183acd95f528719b99")
        
        return true
 }

   // 카카오 로그인 처리를 위한 URL 열기 메서드 추가
     func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
         if AuthApi.isKakaoTalkLoginUrl(url) {
             print("KAKAO 100")
             return AuthController.handleOpenUrl(url: url)
         }
         print("KAKAO 101")
         // 다른 URL 처리를 위한 코드 (필요한 경우)
         return false
     }

위에서 print(“KAKAO 100”) 과 KAKAO 101 모두 출력되지 않는걸로 봐서는 여기까지 오지못하는게 있는 것 같습니다.

mainView.swift 에서는 문제가 없는 것 같은데…
mainView.swift

@main
struct CampApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate


    var body: some Scene {
        WindowGroup {
            SplashScreenView()
        }
    }
}

아래와 같이 구현해 보시겠어요?

SplashScreenView()
    .onOpenURL { url in
        if (AuthApi.isKakaoTalkLoginUrl(url)) {
            ...

감사합니다.
성공적으로 잘 작동하네요.

appDelegate 에서 설정하면 안됬는데,
SplashScreenView().onOpenURL에서 하면 잘 작동하는게 이해가되지 않네요.

아래는 성공한 소스코드입니다.

@main
struct CampApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
        var body: some Scene {
        WindowGroup {
            SplashScreenView()
                .onOpenURL { url in
                                if AuthApi.isKakaoTalkLoginUrl(url) {
                                    print("KAKAO 101")
                                    _ = AuthController.handleOpenUrl(url: url)
                                }
                    print("KAKAO 100")
                            }
        }
    }
}

Deployment target이 어떻게 되시고, 테스트하신 디바이스의 iOS 버전은 어떻게 되시나요?

Deployment target는 default로 되어있고,
(Minimum Deployments 는 16.2)
테스트 기기는 기기는 아이폰15이고, iOS 버전은 17.1.2버전입니다

처음 프로젝트 만들때 appDelegate와 sceneDelegate 없어서 따로 만들었는데
그 과정에서 문제가 있던 걸지…

sceneDelegate 구현하셨다면 앞서 안내드린 가이드와 같이 func scene(_ scene: UI... 내용 구현해 주시고
현재 코드(..view().onOpenURL(...)는 유지하시면 될것 같습니다.