[SwiftUI] Sign in with Apple

애플에서 아주 편한 로그인 기능을 내놨다. 아이폰 쓰는 사람은 애플계정을 갖고 있으니 그 정보로 로그인을 하는데, 개발자가 받을 수 있는 건 이름, 실제/가상 이메일 주소, 임의로 생성된 사용자 고유번호 정도가 전부다.

Xcode의 Signing & capabilities로 가면 +Capability 가 있는데, 이걸 누르면 이런게 뜬다. sign 만 입력해도 Sign in with Apple 이 나오는데, 클릭하면

이렇게 추가가 된다.

애플이 지원하는 Sign in with Apple 이 적힌 버튼을 만들고

import AuthenticationServices
import SwiftUI

final class SignInWithAppleButton: UIViewRepresentable {
  func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
    return ASAuthorizationAppleIDButton(type: .signIn, style: .whiteOutline)
  }

  func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {}
}

화면에 표시한 다음에

import AuthenticationServices
import SwiftUI

struct ContentView: View {
  @State var appleSignInDelegate: SignInWithAppleDelegate! = nil

  var body: some View {
    SignInWithAppleButton().frame(width: 280, height: 60)
      .cornerRadius(5)
      .onTapGesture(perform: showAppleLogin)
  }

  private func showAppleLogin() {
    appleSignInDelegates = SignInWithAppleDelegate {
      print("로그인 성공?: \($0)")
    }

    let request = ASAuthorizationAppleIDProvider().createRequest()
    request.requestedScopes = [.fullName, .email]
 
    let controller = ASAuthorizationController(authorizationRequests: [request])
    controller.delegate = appleSignInDelegate
    controller.presentationContextProvider = appleSignInDelegate
    controller.performRequests()
  }
}

실제 로그인 동작을 할 delegate 를 만들면 된다.

import AuthenticationServices
import SwiftUI

class SignInWithAppleDelegate: NSObject {
  private let signInSucceeded: (Bool) -> Void

  init(onSignedIn: @escaping (Bool) -> Void) {
    signInSucceeded = onSignedIn
  }
}

extension SignInWithAppleDelegate: ASAuthorizationControllerDelegate {
  func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
    switch authorization.credential {
    case let appleIdCredential as ASAuthorizationAppleIDCredential:
      if let _ = appleIdCredential.email, let _ = appleIdCredential.fullName {
        print("111111 ================= 첫 로그인")
        displayLog(credential: appleIdCredential)
      } else {
        print("222222 ================== 로그인 했었음")
        displayLog(credential: appleIdCredential)
      }
      signInSucceeded(true)
      
    default:
      break
    }
  }

  func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {}

  private func displayLog(credential: ASAuthorizationAppleIDCredential) {
    print("identityToken: \(String(describing: credential.identityToken))\nauthorizationCode: \(credential.authorizationCode!)\nuser: \(credential.user)\nemail: \(String(describing: credential.email))\ncredential: \(credential)")
  }
}

extension SignInWithAppleDelegate: ASAuthorizationControllerPresentationContextProviding {
  func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return UIApplication.shared.windows.last!
  }
}

최초 로그인할 때는

이렇게 뜨는데, 만약 나의 이메일 공유하기를 선택하면 실제 메일 주소가 리턴되고, 이메일 가리기를 하면 가상으로 만들어진 alskhfda@privaterelay.appleid.com 이런 메일주소가 리턴된다. 그나마도 이름과 메일주소는 최초 로그인할 때만 받을 수 있고, 다음에 로그인할 때는 토큰주소만 넘어온다. 저 주소로 메일을 보냈는데, 전달에 실패했다. 송신메일서버에서 뭔가 작업을 해야 송신이 된다고 한다.

처음 로그인은 이렇게 로그가 찍힌다.

111111 ================= 첫 로그인
identityToken: Optional(802 bytes)
authorizationCode: 63 bytes
user: 000944.4de0e41477fc4cd6a4a1fbfdbdcd8c29.0504
email: Optional("tt55c74yjy@privaterelay.appleid.com")
credential: <ASAuthorizationAppleIDCredential: 0x2812c3840 { userIdentifier: 000944.4de0e41477fc4cd6a4a1fbfdbdcd8c29.0504, authorizedScopes: (
) }>
로그인 성공?: true

두 번째 로그인부터는 이렇게 표시된다.

로그는 이렇게 찍힌다.

222222 ================== 로그인 했었음
identityToken: Optional(802 bytes)
authorizationCode: 63 bytes
user: 000944.4de0e41477fc4cd6a4a1fbfdbdcd8c29.0504
email: nil
credential: <ASAuthorizationAppleIDCredential: 0x2812c59e0 { userIdentifier: 000944.4de0e41477fc4cd6a4a1fbfdbdcd8c29.0504, authorizedScopes: (
) }>
로그인 성공?: true