Правильная обработка входа, выхода и отзыва учетных данных Apple



В этой статье мы рассмотрим, как вы можете интегрировать «Sign in With Apple» с приложением, использующим аутентификацию Firebase. 
Прежде чем мы начнем, вам нужно подготовиться к нескольким вещам, чтобы мы начали интеграцию.

 

Предпосылки
 

Добавьте Firebase в свой проект iOS

Вам нужно будет добавить Firebase SDK в свой проект iOS и соответствующим образом настроить Firebase Console. Ознакомьтесь с этим руководством от Google для получения дополнительной информации о том, как это можно сделать.


Добавьте «Sign in With Apple» в свой проект iOS

Вам также необходимо настроить свое приложение, чтобы иметь возможность использовать новую функцию «Войти с помощью Apple», представленную в iOS 13. 
Вот отличное руководство по настройке вашего приложения, а также портал для разработчиков Apple.


Я настоятельно рекомендую всем, у кого нет приложения, которое удовлетворяет вышеуказанным требованиям, 
приостановить работу в данный момент и пройти через руководство по настройке Firebase и руководство «Sign in With Apple». 
После того, как вы завершили все настройки, вы можете вернуться к этой статье и узнать, как выполнить интеграцию.


Имея все это в виду, при условии, что вы выполнили настройки, ваше приложение должно иметь возможность отображать форму авторизации Apple, 
которая позволяет пользователям входить в систему с их Apple ID. Кроме того, необходимо правильно установить Firebase SDK.

 

Форма авторизации Apple  


Пришло время перейти к самому интересному - интеграции «Sign in With Apple» с Firebase Authentication.
 

 

 

Включить Apple как метод входа

 

Прежде чем мы перейдем к кодированию, вы должны сначала сообщить Firebase, что хотите использовать Apple в качестве одного из поставщиков услуг входа.

 

Для этого перейдите в консоль Firebase и перейдите на вкладку «Метод входа» в разделе «Аутентификация».

 


Метод входа в Firebase Console

 

Найдите «Apple» в списке поставщиков, а затем нажмите переключатель «Включить», чтобы включить Apple в качестве поставщика услуг входа. 
Вы можете оставить все остальные поля пустыми и затем нажать «Сохранить».

 


Включить Apple в качестве поставщика авторизации

 

Это должно сработать, давайте вернемся в Xcode и начнем работать над кодом.

 

Создать безопасный одноразовый номер

 

Согласно документации Firebase , первый шаг, который нам нужно сделать, - это создать криптографически безопасный одноразовый номер, а затем использовать его при выполнении запроса авторизации от Apple.

 

К счастью, код для генерации одноразового номера предоставлен Firebase.

 

Сначала давайте импортируем модуль CryptoKit.

 

import CryptoKit

 

После этого скопируйте следующие 2 функции в контроллер представления, который содержит кнопку «Sign in With Apple».

 

private func randomNonceString(length: Int = 32) -> String {
    precondition(length > 0)
    let charset: Array =
        Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
    var result = ""
    var rcodeainingLength = length
    
    while rcodeainingLength > 0 {
        let randoms: [UInt8] = (0 ..< 16).map { _ in
            var random: UInt8 = 0
            let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
            if errorCode != errSecSuccess {
                fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
            }
            return random
        }
        
        randoms.forEach { random in
            if rcodeainingLength == 0 {
                return
            }
            
            if random < charset.count {
                result.append(charset[Int(random)])
                rcodeainingLength -= 1
            }
        }
    }
    
    return result
}

private func sha256(_ input: String) -> String {
    let inputData = Data(input.utf8)
    let hashedData = SHA256.hash(data: inputData)
    let hashString = hashedData.compactMap {
        return String(format: "%02x", $0)
    }.joined()
    
    return hashString
}

 

Затем обновите код, в котором вы представляете ASAuthorizationController.

 


let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .codeail]

// Generate nonce for validation after authentication successful
self.currentNonce = randomNonceString()
// Set the SHA256 hashed nonce to ASAuthorizationAppleIDRequest
request.nonce = sha256(currentNonce!)

// Present Apple authorization form
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()

 

После успешной авторизации Apple передаст хешированный одноразовый номер SHA256 без изменений
authorizationController(:didCompleteWithAuthorization:)
методу делегата.

 

В следующем разделе мы рассмотрим, как мы можем использовать одноразовый номер для выполнения аутентификации с помощью Firebase.

 

Обработка входа в Apple успешно завершена

В authorizationController(:didCompleteWithAuthorization:) методе делегата извлеките объект учетных данных из данного ASAuthorization экземпляра.

 


func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {

    if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
        // Do something with the credential...
    }
}

 

После получения объекта учетных данных извлеките идентификатор авторизованного пользователя и сохраните его в формате UserDefaults
Причина сохранения идентификатора пользователя заключается в том, что идентификатор пользователя требуется при выполнении проверки состояния 
учетных данных Apple ID. (подробнее об этом позже)

 


// Save authorised user ID for future reference
UserDefaults.standard.set(appleIDCredential.user, forKey: "appleAuthorizedUserIdKey")

 

Далее нам нужно будет создать объект учетных данных Firebase, чтобы мы могли аутентифицироваться с помощью Firebase. Ниже приведен код для создания объекта учетных данных Firebase.

 


// Retrieve the secure nonce generated during Apple sign in
guard let nonce = currentNonce else {
    fatalError("Invalid state: A login callback was received, but no login request was sent.")
}

// Retrieve Apple identity token
guard let appleIDToken = appleIDCredential.identityToken else {
    print("Failed to fetch identity token")
    return
}

// Convert Apple identity token to string
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
    print("Failed to decode identity token")
    return
}

// Initialize a Firebase credential using secure nonce and Apple identity token
let firebaseCredential = OAuthProvider.credential(withProviderID: "apple.com",
                                                  idToken: idTokenString,
                                                  rawNonce: nonce)

 

После создания объекта учетных данных Firebase аутентификация с помощью Firebase представляет собой простой вызов функции.

 


// Sign in with Firebase
Auth.auth().signIn(with: firebaseCredential) { [weak self] (authResult, error) in
    // Do something after Firebase sign in completed
}

 

Таким образом, вы успешно интегрировали «Sign in With Apple» с Firebase Authentication.


Чтобы проверить всю свою тяжелую работу, нажмите кнопку «Выполнить» и выполните вход в систему, используя образец приложения. После успешного входа в систему перейдите в консоль Firebase, вы должны увидеть, что пользователь был создан.

 


Новый пользователь создан в Firebase Console

 

Обновить отображаемое имя

 

По умолчанию Firebase Authentication не сохраняет отображаемое имя пользователя при создании нового пользователя в консоли Firebase. Однако в большинстве случаев вы хотели бы сохранить отображаемое имя пользователя во время создания пользователя.


К счастью, это можно легко сделать, отправив запрос на изменение профиля пользователя на сервер Firebase.

 


// Mak a request to set user's display name on Firebase
let changeRequest = authResult?.user.createProfileChangeRequest()
changeRequest?.displayName = appleIDCredential.fullName?.givenName
changeRequest?.commitChanges(completion: { (error) in

    if let error = error {
        print(error.localizedDescription)
    } else {
        print("Updated display name: \(Auth.auth().currentUser!.displayName!)")
    }
})

 

Вы можете вставить указанный выше блок кода в обработчик завершения входа Firebase.
При этом Firebase сохранит отображаемое имя пользователя после успешного входа в систему.

 

Вот полная реализация authorizationController(:didCompleteWithAuthorization:) метода делегата.

 


func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
    
    if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
        
        // Save authorised user ID for future reference
        UserDefaults.standard.set(appleIDCredential.user, forKey: "appleAuthorizedUserIdKey")
        
        // Retrieve the secure nonce generated during Apple sign in
        guard let nonce = self.currentNonce else {
            fatalError("Invalid state: A login callback was received, but no login request was sent.")
        }

        // Retrieve Apple identity token
        guard let appleIDToken = appleIDCredential.identityToken else {
            print("Failed to fetch identity token")
            return
        }

        // Convert Apple identity token to string
        guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
            print("Failed to decode identity token")
            return
        }

        // Initialize a Firebase credential using secure nonce and Apple identity token
        let firebaseCredential = OAuthProvider.credential(withProviderID: "apple.com",
                                                          idToken: idTokenString,
                                                          rawNonce: nonce)
            
        // Sign in with Firebase
        Auth.auth().signIn(with: firebaseCredential) { (authResult, error) in
            
            if let error = error {
                print(error.localizedDescription)
                return
            }
            
            // Mak a request to set user's display name on Firebase
            let changeRequest = authResult?.user.createProfileChangeRequest()
            changeRequest?.displayName = appleIDCredential.fullName?.givenName
            changeRequest?.commitChanges(completion: { (error) in

                if let error = error {
                    print(error.localizedDescription)
                } else {
                    print("Updated display name: \(Auth.auth().currentUser!.displayName!)")
                }
            })
        }
        
    }
}

 

Обработка выхода пользователя из системы


Обычная практика выхода из системы аутентификации Firebase состоит из 2 шагов:

 

  1. - Выйдите из системы соответствующего провайдера входа (в нашем случае это Apple)
  2. - Выйти из Firebase

 

Давайте рассмотрим каждый шаг более подробно.

 

Выйти из Apple

 

Согласно документации Apple, Apple не предоставляет API для выхода. Это имеет смысл, потому что пользователи могут выйти из Apple, только отозвав свои учетные данные Apple ID в приложении «Настройки».

 

Следовательно, нам не нужно ничего делать для выхода пользователя из Apple, нам нужно только удалить сохраненный идентификатор пользователя UserDefaults, и все готово.

 


// Check provider ID to verify that the user has signed in with Apple
if
    let providerId = currentUser?.providerData.first?.providerID,
    providerId == "apple.com" {
    // Clear saved user ID
    UserDefaults.standard.set(nil, forKey: "appleAuthorizedUserIdKey")
}

 

Обратите внимание, что если ваше приложение поддерживает вход с использованием нескольких поставщиков входа , может потребоваться дополнительная обработка для получения идентификатора поставщика. Однако я не буду вдаваться в подробности, поскольку это выходит за рамки данной статьи.

 

Выйти из Firebase

 


Чтобы выйти из Firebase, мы можем просто использовать signOut()метод, предоставляемый Firebase SDK.

 

Auth.auth().SignOut()

 


Вот и все, ничего особенного не нужно делать для выхода из Firebase.

 


Вот полная реализация метода выхода.

 


func signOut() throws {
    
    // Check provider ID to verify that the user has signed in with Apple
    if
        let providerId = currentUser?.providerData.first?.providerID,
        providerId == "apple.com" {
        // Clear saved user ID
        UserDefaults.standard.set(nil, forKey: "appleAuthorizedUserIdKey")
    }
    
    // Perform sign out from Firebase
    try Auth.auth().signOut()
}

 

Обработка отзыва учетных данных Apple


Как упоминалось в предыдущем разделе, пользователи могут отозвать учетные данные Apple ID в приложении «Настройки». Когда это произойдет, нам придется очистить сохраненный идентификатор пользователя и выйти из Firebase.

 


При отзыве учетных данных Apple ID необходимо обрабатывать две ситуации:

 

 

  1. - Учетные данные отзываются, когда приложение находится в фоновом режиме.
  2. - Учетные данные аннулируются при завершении работы приложения.

 

Нам придется рассматривать обе упомянутые выше ситуации по отдельности.

 

Обработка отзыва учетных данных, когда приложение работает в фоновом режиме

Согласно документации Apple, уведомление будет отправлено, когда учетные данные Apple ID будут отозваны.
Мы можем использовать это уведомление и соответственно выполнить выход из системы.

 

Идите вперед и зарегистрируйтесь для получения уведомления в своем viewWillAppear(_:) методе контроллера представления.

 


// Register to Apple ID credential revoke notification
NotificationCenter.default.addObserver(self, selector: #selector(appleIDStateDidRevoked(_:)), name: ASAuthorizationAppleIDProvider.credentialRevokedNotification, object: nil)

 

После этого реализуем appleIDStateDidRevoked(_:) метод.

 


@objc func appleIDStateDidRevoked(_ notification: Notification) {
    // Make sure user signed in with Apple
    if
        let providerId = currentUser?.providerData.first?.providerID,
        providerId == "apple.com" {
        signOut()
    }
}

 

Обратите внимание, что нам нужно убедиться, что пользователь действительно вошел в систему Apple, прежде чем вызывать signOut() метод, чтобы избежать случайного выхода пользователя из системы.
Наконец, не забудьте отказаться от уведомления в viewDidDisappear(_:) методе.

 


notificationCenter.rcodeoveObserver(self, name: ASAuthorizationAppleIDProvider.credentialRevokedNotification, object: nil)

 

Обработка отзыва учетных данных при завершении работы приложения

Когда приложение закрывается, получать уведомления невозможно. Следовательно, мы не можем полагаться на то, credentialRevokedNotification чтобы уведомить нас о действии отзыва.

В этом случае нам придется вручную проверять состояние учетных данных Apple ID во время запуска приложения. Чтобы проверить состояние учетных данных Apple ID, мы можем использовать getCredentialState(forUserID::) метод, предоставленный Apple.

 


// Retrieve user ID saved in UserDefaults
if let userID = UserDefaults.standard.string(forKey: "appleAuthorizedUserIdKey") {
    
    // Check Apple ID credential state
    ASAuthorizationAppleIDProvider().getCredentialState(forUserID: userID, completion: { [unowned self]
        credentialState, error in
        
        switch(credentialState) {
        case .authorized:
            break
        case .notFound,
             .transferred,
             .revoked:
            // Perform sign out
            try? self.signOut()
            break
        @unknown default:
            break
        }
    })
}

 

В целях тестирования вы можете поместить указанный выше блок кода в viewDidLoad() метод контроллера представления, который содержит кнопку «Войти с помощью Apple».

Тем не менее, в большинстве случаев, вы можете добавить его в AppDelegate«S application(_:didFinishLaunchingWithOptions:) метода.

 

Вот и все, ваше приложение теперь полностью интегрировано с функцией «Sign in With Apple» с аутентификацией Firebase.

 

Выводы

 

Шаги, необходимые для интеграции «Sign in With Apple» с Firebase, на самом деле довольно просты. Напомним, что мы сделали для интеграции:

 

 

  1. - Включите Apple в качестве поставщика входа в Firebase Console.
  2. - Войдите в Firebase после того, как пользователи успешно вошли в систему с Apple.
  3. - Удалите сохраненный идентификатор пользователя после выхода пользователей из Firebase.
  4. - Выйдите из Firebase, если пользователи отозвали учетные данные Apple ID, когда приложение работает в фоновом режиме.
  5. - Выйдите из Firebase во время запуска приложения, если пользователи отозвали учетные данные Apple ID, когда приложение было закрыто.

 

Обратите внимание, что в этой статье предполагается, что Apple является единственным поставщиком услуг входа в приложение. Если ваше приложение имеет более одного поставщика услуг входа, вам может потребоваться изменить соответствующий пример кода соответствующим образом.

 

Дополнительная литература

 

 

Я надеюсь, что эта статья даст вам хорошее и четкое представление о том, как интегрировать «Sign in With Apple» с Firebase Authentication.

 

Если у вас есть какие-либо вопросы, оставьте их в разделе комментариев ниже. Если вы найдете эту статью полезной, не стесняйтесь поделиться ею со своими друзьями и коллегами.

 

 

Спасибо за прочтение.