🔐 Sign in with Apple
가장 프라이버시 친화적인 로그인. 이메일 숨김, 2FA 자동, Face ID 인증!
⭐ 난이도: ⭐⭐
⏱️ 예상 시간: 1-2h
📂 App Services
✨ Sign in with Apple이란?
Apple ID로 간편 로그인합니다. 이메일을 숨길 수 있고, 2단계 인증이 자동으로 처리됩니다. 다른 소셜 로그인이 있다면 필수 구현입니다.
🎯 기본 구현
SignInManager.swift
import AuthenticationServices class SignInManager: NSObject, ASAuthorizationControllerDelegate { func signInWithApple() { let provider = ASAuthorizationAppleIDProvider() let request = provider.createRequest() // 요청할 정보 (이름, 이메일) request.requestedScopes = [.fullName, .email] let controller = ASAuthorizationController(authorizationRequests: [request]) controller.delegate = self controller.presentationContextProvider = self controller.performRequests() } // 성공 시 func authorizationController( controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization ) { if let credential = authorization.credential as? ASAuthorizationAppleIDCredential { let userID = credential.user // 고유 ID (변하지 않음) let email = credential.email // 첫 로그인 시만 제공 let fullName = credential.fullName // 첫 로그인 시만 제공 let identityToken = credential.identityToken // JWT 토큰 // 백엔드로 identityToken 전송하여 검증 if let tokenData = identityToken, let tokenString = String(data: tokenData, encoding: .utf8) { verifyTokenWithBackend(tokenString, userID: userID) } // UserDefaults에 userID 저장 UserDefaults.standard.set(userID, forKey: "appleUserID") } } // 실패 시 func authorizationController( controller: ASAuthorizationController, didCompleteWithError error: Error ) { print("로그인 실패: \(error.localizedDescription)") } func verifyTokenWithBackend(_ token: String, userID: String) { // 백엔드 API 호출 } } extension SignInManager: ASAuthorizationControllerPresentationContextProviding { func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { return UIApplication.shared.windows.first! } }
🎨 SwiftUI에서 Sign in with Apple 버튼
SignInButton.swift
import SwiftUI import AuthenticationServices struct SignInWithAppleButton: View { @Environment(\.colorScheme) var colorScheme var body: some View { SignInWithAppleButton( .signIn, onRequest: { request in request.requestedScopes = [.fullName, .email] }, onCompletion: { result in switch result { case .success(let authorization): handleAuthorization(authorization) case .failure(let error): print("에러: \(error)") } } ) .signInWithAppleButtonStyle( colorScheme == .dark ? .white : .black ) .frame(height: 50) } func handleAuthorization(_ authorization: ASAuthorization) { if let credential = authorization.credential as? ASAuthorizationAppleIDCredential { print("사용자 ID: \(credential.user)") print("이메일: \(credential.email ?? \"없음\")") } } } // 사용 예시 struct LoginView: View { var body: some View { VStack(spacing: 20) { Text("로그인") .font(.largeTitle.bold()) SignInWithAppleButton() .padding(.horizontal) } } }
🔄 자동 로그인 상태 체크
CredentialCheck.swift
import AuthenticationServices func checkCredentialState() { guard let userID = UserDefaults.standard.string(forKey: "appleUserID") else { // 저장된 userID 없음 return } let provider = ASAuthorizationAppleIDProvider() provider.getCredentialState(forUserID: userID) { state, error in switch state { case .authorized: print("로그인 유지 중") // 자동 로그인 처리 case .revoked: print("로그인 취소됨") // 로그아웃 처리 UserDefaults.standard.removeObject(forKey: "appleUserID") case .notFound: print("사용자 없음") case .transferred: print("다른 팀으로 이전됨") @unknown default: break } } } // 앱 시작 시 체크 func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { checkCredentialState() return true }
📧 이메일 릴레이 (Email Relay)
사용자가 "이메일 숨기기"를 선택하면 Apple이 프록시 이메일(`privaterelay@icloud.com`)을 제공합니다.
EmailRelay.swift
func handleCredential(_ credential: ASAuthorizationAppleIDCredential) { if let email = credential.email { // 첫 로그인 시만 제공됨 print("이메일: \(email)") // privaterelay@icloud.com 형태면 프록시 이메일 if email.contains("privaterelay") { print("사용자가 이메일을 숨김") } // ⚠️ 중요: 첫 로그인 시만 제공되므로 반드시 저장! UserDefaults.standard.set(email, forKey: "userEmail") } }
🔑 백엔드 토큰 검증
BackendVerification.swift
// identityToken은 JWT 형식입니다 // 백엔드에서 Apple 공개키로 검증해야 합니다 func sendTokenToBackend(_ credential: ASAuthorizationAppleIDCredential) async { guard let tokenData = credential.identityToken, let token = String(data: tokenData, encoding: .utf8) else { return } // authorizationCode도 백엔드로 전송 (refresh token 발급용) guard let codeData = credential.authorizationCode, let code = String(data: codeData, encoding: .utf8) else { return } // API 호출 let body: [String: Any] = [ "identityToken": token, "authorizationCode": code, "user": credential.user ] // POST to your backend } /* 백엔드 검증 (Node.js 예시) const jwt = require('jsonwebtoken'); const jwksClient = require('jwks-rsa'); const client = jwksClient({ jwksUri: 'https://appleid.apple.com/auth/keys' }); function getKey(header, callback) { client.getSigningKey(header.kid, (err, key) => { callback(null, key.publicKey || key.rsaPublicKey); }); } jwt.verify(identityToken, getKey, (err, decoded) => { if (err) { // 토큰 검증 실패 } else { // decoded.sub === userID // decoded.email === 사용자 이메일 } }); */
📱 기존 계정 업그레이드
AccountUpgrade.swift
import AuthenticationServices // 기존 이메일/비밀번호 로그인을 Apple ID로 업그레이드 func upgradeToAppleID(existingEmail: String) { let provider = ASAuthorizationAppleIDProvider() let request = provider.createRequest() request.requestedScopes = [.fullName] // 기존 계정 연결을 위한 힌트 request.user = existingEmail let controller = ASAuthorizationController(authorizationRequests: [request]) // ... delegate 설정 controller.performRequests() }
🚨 로그아웃 & 계정 삭제
Revocation.swift
// 로그아웃 func signOut() { UserDefaults.standard.removeObject(forKey: "appleUserID") UserDefaults.standard.removeObject(forKey: "userEmail") // UI를 로그인 화면으로 } // 계정 삭제 (백엔드에서 Apple API 호출 필요) /* 사용자가 "설정 > Apple ID > 암호 및 보안 > Apple로 로그인을 사용하는 앱"에서 앱을 제거하면 로그인이 취소됩니다. 백엔드에서는 Apple의 Token Revocation API를 호출해야 합니다: POST https://appleid.apple.com/auth/revoke 이후 사용자 데이터를 삭제하고 계정을 비활성화합니다. */
⚙️ 앱 설정 (Capabilities)
Xcode 설정
1. Xcode에서 프로젝트 선택
2. Signing & Capabilities 탭
3. "+ Capability" 클릭
4. "Sign in with Apple" 추가
5. Apple Developer 콘솔에서:
- Identifier에 "Sign in with Apple" 활성화
- Services ID 생성 (웹 로그인용, 선택)
- Key 생성 (백엔드 검증용)✅ 필수 요구사항 (App Review)
⚠️ 필수!
앱에 다른 소셜 로그인(Google, Facebook 등)이 있다면,
Sign in with Apple을 반드시 구현해야 합니다.
그렇지 않으면 App Store 심사에서 리젝됩니다.
💡 HIG 체크리스트
✅ 공식 Sign in with Apple 버튼 사용
✅ 다른 로그인 버튼과 동일한 크기/위치
✅ 이메일/이름은 첫 로그인 시만 제공됨 (저장 필수)
✅ 토큰은 백엔드에서 검증
✅ 앱 시작 시 credential 상태 체크