사비성(sabisung)의 컴퓨터

40개의 항목
 

애플 로그인 구현

왜?

다양한 앱들에서 이미 가입된 SNS 계정을 사용하여 회원가입을 유도하고 있다.
이미 검증된 사용자 정보를 얻기 쉬우니깐… ㅋㅋㅋ

Xcode 설정

구현하기 전에 Xcode의 앱 설정에서 애플 로그인 기능을 사용하겠다고 설정한다.
이미지 #1

구현

//
//  ViewController.swift
//  AppleLogin
//
//  Created by sabisung on 2021/06/17.
//

import UIKit
import AuthenticationServices
import JWTDecode

class ViewController: UIViewController {
    
    struct UserInfo: Codable {
        let id: String
        let email: String?
        let name: String?
        let authCode: String
        var dictionary: [String: String] {
            return [
                "id": id,
                "email": email ?? "",
                "name": name ?? "",
                "authCode": authCode,
            ]
        }
    }
    
    /// 로그인 플래그 값
    private let appleLoginFlagKey = "applelogin.user.islogin"

    /// 로그인 사용자 정보
    private let appleLoginUserInfoKey = "applelogin.user.info"

    @IBOutlet weak var appleLoginView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        //애플 로그인 버튼
        let appleLoginBtn = ASAuthorizationAppleIDButton(authorizationButtonType: .signIn, authorizationButtonStyle: .black)
        appleLoginBtn.addTarget(self, action: #selector(appleLoginBtnHander), for: .touchUpInside)
        appleLoginView.addSubview(appleLoginBtn)
    }
    
    /// 애플 로그인 버튼 핸들러
    /// - Parameter sender: UIButton
    @objc func appleLoginBtnHander(_ sender: UIButton) {
        let request = ASAuthorizationAppleIDProvider().createRequest()
        request.requestedScopes = [.email, .fullName]
        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.presentationContextProvider = self
        controller.performRequests()
    }

    /// 로그인 정보를 UserDefaults에 저장
    /// - Parameters:
    ///   - isLogin: 로그인 여부
    ///   - userInfo: UserInfo 구조체
    private func saveUserInfo(isLogin: Bool, userInfo: UserInfo?) {
        let defaults = UserDefaults.standard
        defaults.set(isLogin, forKey: appleLoginFlagKey)
                
        if isLogin {
            if let jsonData = try? JSONEncoder().encode(userInfo), let jsonString = String.init(data: jsonData, encoding: .utf8) {
                defaults.set(jsonString, forKey: appleLoginUserInfoKey)
            }
        } else {
            defaults.removeObject(forKey: appleLoginUserInfoKey)
        }
    }
    
    /// UserDefaults에 저장된 사용자 정보 반환
    /// - Returns: UserInfo 구조체
    private func getUserInfo() -> UserInfo? {
        let userInfo = UserDefaults.standard.string(forKey: appleLoginUserInfoKey)
        guard let userInfoData = userInfo?.data(using: .utf8) else {
            return nil
        }
        return try! JSONDecoder().decode(UserInfo.self, from: userInfoData)
    }
}

extension ViewController: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        //return UIApplication.shared.keyWindow!
        return UIApplication.shared.windows.filter {$0.isKeyWindow}.first!
    }
    
    /// 애플 로그인 실패
    /// - Parameters:
    ///   - controller: ASAuthorizationController 객체
    ///   - error: Error 객체
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        print("\(error.localizedDescription)")
    }
    
    /// 애플 로그인 인증 성공
    /// - Parameters:
    ///   - controller: ASAuthorizationController 객체
    ///   - authorization: ASAuthorization 객체
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else {
            saveUserInfo(isLogin: false, userInfo: nil)
            return
        }
        let user: String = credential.user
        var name: String = "\(credential.fullName?.familyName ?? "")\(credential.fullName?.givenName ?? "")"
        if name.count == 0, let savedUserInfo = self.getUserInfo() {
            name = savedUserInfo.name ?? ""
        }
        let authCode: String = "\(String(data: credential.authorizationCode ?? Data(), encoding: .utf8) ?? "")"
        let identifyToken: String = "\(String(data: credential.identityToken ?? Data(), encoding: .utf8) ?? "")" //JWT Token. 항상 email 주소 존재
            
        // identifyToken값내의 email을 사용.
        guard let jwtJson = try? JWTDecode.decode(jwt: identifyToken), let email = jwtJson.body["email"] as? String else {
            saveUserInfo(isLogin: false, userInfo: nil)
            return
        }
        saveUserInfo(isLogin: true, userInfo: UserInfo(id: user, email: email, name: name, authCode: authCode))
        print("User Info: \(getUserInfo())")
    }
}

잠깐!!

애플 로그인 인증 후 사용자 정보가 내려오는데 최초 인증시 내려주는 정보와 이후 인증 후 내려주는 정보의 항목이 다르다.
이름과 이메일 정보는 최초 인증 후에만 내려주고 이후 인증에서는 내려주지 않는다.
다만, 이메일 정보는 JWT를 Decoding하여 항상 얻을 수 있다.
따라서, 이름 정보가 필요할 경우에 대비하여 최초 인증 후 이름을 저장하여 사용한다.

샘플 소스 다운로드

Sample Source Download (AppleLogin.zip)