Салют, меня зовут Макс Нечаев, я занимаюсь продуктовой разработкой (iOS Developer). Сегодня решил сделать небольшую статью, которая расскажет, как подключить Apple Sign In в ваше iOS приложение. Плюс расскажу некоторые edge кейсы технологии.
UI часть задачи
Заходим в наше View. Первое, что нам нужно, это импортировать сервисы авторизации.
import AuthenticationServices
Вопреки расхожему мифу, можно сделать кастомную кнопку авторизации Apple, а не использовать ASAuthorizationAppleIDButton. Так я и поступил. Я взял главную кнопку, которая используется в приложении и добавил ей Apple стилистику по дизайну. После чего накинул на нее классический таргет.
private let appleButton = PrimaryButton(style: .appleSignIn)
//
appleButton.addTarget(self, action: #selector(loginWithApple), for: .touchUpInside)
По дизайну в нашем приложении кнопка находится внизу логин страницы, поэтому я добавил на кнопку плавающий нижний констрейнт (верстка через SnapKit). При открытии и закрытии клавиатуры, я анимационно меняю нижний констрейнт, что позволяет кнопке всегда держаться над клавиатурой. Так, с точки зрения UX пользователь не теряет возможности использовать Apple для авторизации вне зависимости от состояния клавиатуры.
appleButton.snp.makeConstraints {
$0.leading.equalToSuperview().offset(16)
$0.trailing.equalToSuperview().offset(-16)
$0.height.equalTo(56)
appleButtonBottomConstraint = $0.bottom
.equalTo(view.safeAreaLayoutGuide.snp.bottom)
.offset(-16)
.constraint
}
Мы используем VIPER, следовательно всю логику Apple авторизации я решил вынести в отдельный сервис, доступ к этому сервису будет иметь Interactor. Вся задача View (в качестве View у нас ViewController), это поймать нажатие на кнопку и сказать Presenter об этом. Presenter, в свою очередь, оповещает Interactor, тот обращается к сервису, который уже обрабатывает всю логику. После чего приложение меняет свое состояние и выполняется обратная цепочка от Interactor к View.
Проще говоря, инкапсулируем всю логику авторизации в отдельный сервис. Сейчас я покажу, как реализовал его.
Реализация сервиса авторизации
В качестве доступа к сервису использовал синглтон. Многие скажут, что не стоит так делать, отчасти согласен. Но в нашем проекте некоторые сервисы работают через него, все в порядке. Вы можете реализовать другой тип зависимостей.
final class AppleAuthManager: NSObject {
static let shared = AppleAuthManager()
Создаем структуру данных, которые нам понадобятся для отправки запроса авторизации на сервер.
struct AppleAuthData {
let identityToken: String
let authorizationCode: String
let firstName: String?
let lastName: String?
}
Я пошел классическим путем обработки через completion handler. Соответственно создаем приватную переменную с enum Result, который будет возвращать либо нашу заполненную модель, либо ошибку.
// MARK: - Private properties
private var completionHandler: ((Result<AppleAuthData, Error>) -> Void)?
Весь доступ к сервису осуществляется через публичный метод login, который принимает в себя такой же клоужер, как мы создали ранее. Ниже в комментариях к коду добавлю пояснения.
// MARK: - Public methods
func login(completion: @escaping (Result<AppleAuthData, Error>) -> Void) {
// сетим в наш хэндлер комплишн функции
self.completionHandler = completion
// создаем провайдер и запрос, говорим запросу тот скоуп данных,
// которые он должен предоставить
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
// сетим в контроллер авторизации наш запрос
// подписываемся на делегат, который и даст нам то, что нужно, и делаем запрос
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.performRequests()
}
Далее самое главное, реализуем делегат ASAuthorizationControllerDelegate. Покажу сначала простую часть. Если авторизация по какой-либо причине закончилась с ошибкой, то просто передаем хэндлеру эту ошибку, её нам нужно будет показать в отдельном алерте.
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithError error: Error
) {
completionHandler?(.failure(error))
}
Наконец мы дошли до самого интересного метода делегата didCompleteWithAuthorization. Именно здесь мы должны сначала через guard let получить данные входа (credential), а также необходимый нам identityTokenData, который следует привести к String формату через (encoding: .utf8). На словах может быть непонятно, поэтому конечно же предоставляю сниппет кода.
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
// получаем и кастим данные
guard
let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
let authorizationCodeData = credential.authorizationCode,
let identityTokenData = credential.identityToken,
let authorizationCode = String(data: authorizationCodeData, encoding: .utf8),
let identityToken = String(data: identityTokenData, encoding: .utf8)
else {
return
}
// приводим их к нашему типу
let data = AppleAuthData(
identityToken: identityToken,
authorizationCode: authorizationCode,
firstName: credential.fullName?.givenName,
lastName: credential.fullName?.familyName
)
// вызываем комплишн и передаем в него данные
completionHandler?(.success(data))
}
Важные моменты
Apple предоставляет имя и фамилию пользователя только один раз. В самый первый раз, когда пользователь входит через Apple. Обязательно отлавливайте эти данные и передавайте их на сервер в базу данных.
При авторизации есть возможность скрыть свой email (”Hide my email”), в таком случае Apple сгенерирует кастомный email, который будет привязан к пользователю. То есть совсем без email вы не останетесь, просто будет выбор из двух: персональная почта или почта apple.
Концовка
Теперь вам останется только обратиться к сервису в Interactor (если используете VIPER), и в комплишн хэндлере обработать события. Если успешно - отправляете запрос на сервер с нужными данными авторизации, если нет, выводите алерт. Плюс ко всему этому можно подключить аналитику.
Я постарался подробно, шаг за шагом описать, как вы можете подключить Apple авторизацию в свой проект, надеюсь, что это кому-то поможет.
Спасибо за ваше время, оно бесценно. Если есть пожелания или вопросы, пишите здесь в комментарии или мне в телеграм @maxnech.
Хорошего дня!