Goal
애플 로그인과 그 방식에 대한 이해
WWDC19에서 발표된 애플 사용자를 위한 새로운 SSO 서비스입니다
애플 로그인은 iOS 13.0 이상부터 사용할 수 있습니다
타 SNS 로그인 방식을 구현하여 앱에 적용할 경우
애플 로그인은 필수로 구현되어야 합니다
Sign In With Apple 의 특징으로는
유저가 이중인증으로 로그인하면 ID, Full Name, Verified email address를 받아올 수 있습니다
또한 원본 이메일을 공유하거나 private한 이메일을 제공할것인지 유저가 선택할 수 있습니다
인증과정으로는 아래 그림과 같습니다
앱에서의 동작방식
구현 방식은 애플 Sample code 문서인
Implementing User Authentication with Sign in with Apple 를 참고했습니다
let authorizationButton = ASAuthorizationAppleIDButton()
authorizationButton.addTarget(self, action: #selector(handleAuthorizationAppleIDButtonPress), for: .touchUpInside)
self.loginProviderStackView.addArrangedSubview(authorizationButton)
먼저 버튼을 구현합니다
@objc
func handleAuthorizationAppleIDButtonPress() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
이 함수는 Sign In With Apple 버튼이 클리되었을 때 호출되는데
fullName과 email 정보를 요구할 수 있습니다
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
인증창을 어느화면에 보여줄지 정하는 메서드입니다
인증결과를 다루기 위해 ASAuthorizationControllerDelegate 를 채택합니다
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {}
그러면 인증이 성공했을 때와 실패했을 경우 처리할 수 있는 메서드들이 있습니다
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
// Create an account in your system.
let userIdentifier = appleIDCredential.user
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email
// For the purpose of this demo app, store the `userIdentifier` in the keychain.
self.saveUserInKeychain(userIdentifier)
// For the purpose of this demo app, show the Apple ID credential information in the `ResultViewController`.
self.showResultViewController(userIdentifier: userIdentifier, fullName: fullName, email: email)
case let passwordCredential as ASPasswordCredential:
// Sign in using an existing iCloud Keychain credential.
let username = passwordCredential.user
let password = passwordCredential.password
// For the purpose of this demo app, show the password credential as an alert.
DispatchQueue.main.async {
self.showPasswordCredentialAlert(username: username, password: password)
}
default:
break
}
}
문서의 코드를 빌려, 인증에 성공하면 요청한 정보들인 UserID와 fullName, email 등을 받을 수 있습니다.
UserID는 고정값이기 때문에 바뀌지 않습니다.
이 UserID를 키체인에 저장하고 getCredentialState메서드 파라미터에 값을 넣는 방식으로 자동로그인을 구현할 수 있습니다.
let appleIDProvider = ASAuthorizationAppleIDProvider()
appleIDProvider.getCredentialState(forUserID: KeychainItem.currentUserIdentifier) { (credentialState, error) in
switch credentialState {
case .authorized:
break // The Apple ID credential is valid.
case .revoked, .notFound:
// The Apple ID credential is either revoked or was not found, so show the sign-in UI.
DispatchQueue.main.async {
self.window?.rootViewController?.showLoginViewController()
}
default:
break
}
}
또한 appleIDCredential을 통해 identityToken을 받아올 수 있는데
토큰은 JWT형식으로 구성되어있으며 많은 claim들을 포함하고 있습니다
iss : 토큰 발급자로 애플 로그인이기 때문에 https://appleid.apple.com 값을 가진다.
sub : 토큰 제목, 사용자를 위한 유일 값을 가진다. user identity가 sub 값으로 존재한다.
aud : 토큰 대상자
iat : 토큰이 발급된 시간 (UTC)
exp : 토큰 만료 시간 (UTC), 토큰을 확인할 때 값이 현재 날짜/시간보다 커야한다.
nonce : 클라이언트 세션과 ID 토큰을 연결하는 데 사용되는 문자열 값이다.
nonce_supported : 트랜잭션이 지원되지 않는 플랫폼에 있는지 여부를 나타내는 부울 값입니다.
email : 사용자 이메일 주소를 나타낸다. 비공개 이메일이면 privaterelay가 붙는다.
email_verified : 이메일이 검증되었는지 여부를 나타낸다. Bool 값을 String으로 전달한다.
is_private_email : 이메일이 비공개인지 여부를 나타낸다. Bool 값을 String으로 전달한다.
real_user_status : 사용자가 실제 사람인지를 포함한 값을 정수 형태로 나타낸다.
String(data: appleIDCredetial.identityToken!, encoding: .utf8)
이 값을 https://jwt.io 에서 encoded에 넣으면 decoded된
clames를 볼 수 있습니다