Салют, меня зовут Макс Нечаев, я занимаюсь продуктовой разработкой (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.

Хорошего дня!

Комментарии (1)


  1. proea
    02.11.2022 19:11

    Уважаемый автор. Это аутентификация, а не авторизация