Apple announced "Sign in with Apple" in the WWDC 2019. They offer iOS users the opportunity to create an account easy and fast without any cumbersome process steps like filling out a bunch of input fields with personal data and tapping an activation link in a received email. To use Sign in with Apple you just need an Apple-ID.
Sign In with Apple works on iOS, macOS, tvOS, and watchOS. You can also add Sign In with Apple to your website or versions of your app running on other platforms. Once a user sets up their account, they can sign in anywhere you deploy your app.
Apple added in their App Store Review Guidelines that the apps who have already 3rd party social logins (Facebook Login, Sign in with Twitter, e.t.c) to set up an account, must also add the "Sign in with Apple" method.
Developers can setup the requests so that the app can only receive the name and the email of the user and a unique opaqued user identifier.
To use Sign in with Apple as an authentication service for an app or website the user must enable the two-factor authentication for their Apple-ID.
Before getting started first of all Sign in with Apple must be enabled in your app. Go into your project and choose your project from the left side, press Signing & Capabilities, and then press + Capability to add a new capability. On the new window double click Sign in with Apple to add it. This will add an entitlement that lets your app use Sign In with Apple.
AuthenticationServices is the framework which needs to be used to perform anything for Sign in with Apple. Following are the usual classes with their functions of this framework:
ASAuthorizationAppleIDProvider. Is a mechanism to generate the requests to authenticate the user with his Apple-ID.ASAuthorizationController. A controller which needs to be initialized with the requests (ASAuthorizationAppleIDRequest) and performs them. With the corresponding delegate methods you can get the credentials (ASAuthorizationAppleIDCredential) at success.ASAuthorizationAppleIDCredential. The credential object contains following properties: identityToken, authorizationCode, state, user.AuthenticationServices framework provides ASAuthorizationAppleIDButton to enables users to initiate the "Sign In with Apple" flow.
let appleButton = ASAuthorizationAppleIDButton()
Now on the press of "Sign In with Apple" button, we need to use a ASAuthorizationAppleIDProvider to create a ASAuthorizationAppleIDRequest, which we then use to initialize a ASAuthorizationController that performs the request.
@objc private func handleSignIn() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
On success, the ASAuthorizationController delegate receives an authorization (ASAuthorization) containing a credential (ASAuthorizationAppleIDCredential) that has an opaque user identifier.
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// handle error here
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
// create an account in your system.
let userIdentifier = appleIDCredential.user
let userFirstName = appleIDCredential.fullName?.givenName
let userLastName = appleIDCredential.fullName?.familyName
let userEmail = appleIDCredential.email
// navigate to other view controller
}
}
// tells the delegate from which window it should present content to the user
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
Check Credential State. We can use that userIdentifier which we got from ASAuthorizationAppleIDCredential object to check the user credential state. We can get credential state for an userIdentifier by calling the getCredentialState(forUserID: completion:) method:
let appleIDProvider = ASAuthorizationAppleIDProvider()
appleIDProvider.getCredentialState(forUserID: userIdentifier) { (credentialState, error) in
switch credentialState {
case .authorized:
// The Apple ID credential is valid. Show Home UI Here
break
case .revoked:
// The Apple ID credential is revoked. Show SignIn UI Here.
break
case .notFound:
// No credential was found. Show SignIn UI Here.
break
default:
break
}
}
Remove Existing Account from your Apple ID. The reason email and name might be nil is that user might decline to reveal these information during the Apple sign-in prompt, or the user has already signed in previously.
For testing the sign up process you can force revoke the state of the user instead of waiting. This can be done in the iOS System settings → iCloud settings → Password & Security → Apple ID logins. Your app should be listed there and then you can stop using your Apple ID for your app.
UIKit
To easy AutoLayout I'm going to use SnapKit and add all the UI elements into the view controller programmatically.
import UIKit
import SnapKit
import AuthenticationServices
struct DefaultKeys {
static let userID = "userID"
static let firstName = "firstName"
static let lastName = "lastName"
static let email = "email"
}
class ViewController: UIViewController {
let statusLabel = UILabel()
let signInButton: ASAuthorizationAppleIDButton = {
//let v = ASAuthorizationAppleIDButton(type: .signIn, style: .black)
let v = ASAuthorizationAppleIDButton()
v.addTarget(self, action: #selector(handleSignIn), for: .touchUpInside)
return v
}()
lazy var stackView: UIStackView = {
let v = UIStackView(arrangedSubviews: [statusLabel, signInButton])
v.axis = .vertical
v.distribution = .fillEqually
v.spacing = 8
v.alignment = .center
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
// call the function appleIDStateRevoked if user revoke the sign in in Settings app
NotificationCenter.default.addObserver(self, selector: #selector(appleIDStateRevoked), name: ASAuthorizationAppleIDProvider.credentialRevokedNotification, object: nil)
}
private func setupView() {
view.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.center.equalToSuperview()
}
updateScreen()
}
private func updateScreen() {
statusLabel.text = "Loading ..."
if let userID = UserDefaults.standard.string(forKey: DefaultKeys.userID) {
ASAuthorizationAppleIDProvider().getCredentialState(forUserID: userID, completion: { [weak self]
credentialState, error in
switch(credentialState){
case .authorized:
print("user remain logged in, proceed to another view")
let firstName = UserDefaults.standard.string(forKey: DefaultKeys.firstName) ?? "-"
let lastName = UserDefaults.standard.string(forKey: DefaultKeys.lastName) ?? "-"
DispatchQueue.main.async {
self?.statusLabel.text = "Hello \(firstName) \(lastName)"
self?.signInButton.isHidden = true
}
case .revoked:
print("user logged in before but revoked")
self?.notLoggedState()
case .notFound:
print("user haven't log in before")
self?.notLoggedState()
default:
print("unknown state")
}
})
} else {
notLoggedState()
}
}
private func notLoggedState() {
DispatchQueue.main.async {
self.statusLabel.text = "Please sign in"
self.signInButton.isHidden = false
}
UserDefaults.standard.removeObject(forKey: DefaultKeys.userID)
}
@objc func handleSignIn() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
@objc func appleIDStateRevoked() {
notLoggedState()
}
}
extension ViewController: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
}
extension ViewController: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
print("authorization error")
guard let error = error as? ASAuthorizationError else {
return
}
switch error.code {
case .canceled:
// user press "cancel" during the login prompt
print("Canceled")
case .unknown:
// user didn't login their Apple ID on the device
print("Unknown")
case .invalidResponse:
// invalid response received from the login
print("Invalid Respone")
case .notHandled:
// authorization request not handled, maybe internet failure during login
print("Not handled")
case .failed:
// authorization failed
print("Failed")
@unknown default:
print("Default")
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
let userID = appleIDCredential.user
print("User ID: \(userID)")
let email = appleIDCredential.email ?? ""
print("Email: \(email)")
let givenName = appleIDCredential.fullName?.givenName ?? ""
print("Name: \(givenName)")
let familyName = appleIDCredential.fullName?.familyName ?? ""
print("Family Name: \(familyName)")
let nickName = appleIDCredential.fullName?.nickname ?? ""
print("Nick name: \(nickName)")
UserDefaults.standard.set(userID, forKey: DefaultKeys.userID)
UserDefaults.standard.set(givenName, forKey: DefaultKeys.firstName)
UserDefaults.standard.set(familyName, forKey: DefaultKeys.lastName)
UserDefaults.standard.set(email, forKey: DefaultKeys.email)
updateScreen()
}
}
}
SwiftUI
import SwiftUI
import AuthenticationServices
struct ContentView: View {
@AppStorage("storedName")
private var storedName : String = "" {
didSet {
userName = storedName
}
}
@AppStorage("storedEmail")
private var storedEmail : String = "" {
didSet {
userEmail = storedEmail
}
}
@AppStorage("userID")
private var userID : String = ""
@State
private var userName: String = ""
@State
private var userEmail: String = ""
var body: some View {
ZStack{
Color.white
if userName.isEmpty {
SignInWithAppleButton(.signIn, onRequest: onRequest, onCompletion: onCompletion)
.signInWithAppleButtonStyle(.black)
.frame(width: 200, height: 50)
} else {
Text("Welcome\n\(userName), \(userEmail)")
.foregroundColor(.black)
.font(.headline)
}
}
.onAppear(perform: onAppear)
}
private func onRequest(_ request: ASAuthorizationAppleIDRequest) {
request.requestedScopes = [.fullName, .email]
}
private func onCompletion(_ result: Result<ASAuthorization, Error>) {
switch result {
case .success (let authResults):
guard let credential = authResults.credential
as? ASAuthorizationAppleIDCredential
else { return }
storedName = credential.fullName?.givenName ?? ""
storedEmail = credential.email ?? ""
userID = credential.user
case .failure (let error):
print("Authorization failed: " + error.localizedDescription)
}
}
private func onAppear() {
guard !userID.isEmpty else {
userName = ""
userEmail = ""
return
}
ASAuthorizationAppleIDProvider()
.getCredentialState(forUserID: userID) { state, _ in
DispatchQueue.main.async {
if case .authorized = state {
userName = storedName
userEmail = storedEmail
} else {
userID = ""
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Register Domains and Emails for communication
The most interesting feature of Sign in with Apple is the private Apple Relay E-Mail service. Apple focusses on data privacy of the users so when the users get requested for providing their name and email they have the opportunity to hide or share their real email address with the app/website. If they choose to hide, Apple’s Relay system generates a random unique email address which routes all incoming messages and emails to the real email address of the user without the app/website knowing the real email address.
In order to contact users that use Apple's private email relay service, you need to register domains and email addresses that your organization will use for communication. To config this, open your Apple Developer Account. Now, click on More side menu on the Certificates, Identifiers & Profiles page.