Внедряем Sign in with Apple в свое iOS приложение

  • Tutorial
На WWDC 2019 Apple в очередной раз нарушила покой iOS разработчиков — представила новую систему авторизации пользователей Sign in with Apple. Теперь все iOS приложения, которые используют сторонние системы авторизации (Facebook, Twitter, etc.), должны в обязательном порядке реализовать Sign in with Apple, иначе выгонят из AppStore. Мы решили не испытывать судьбу и побежали внедрять эту фичу. Как именно мы это сделали — узнаете под катом.

Пишем сервис авторизации через Apple


В своей работе мы используем архитектуру VIPER+SOA, поэтому авторизацию через Apple мы сделали как отдельный сервис. Сначала мы оборачиваем данные в enum, чтобы удобно расширять типы авторизации (фейсбук, вк, гугл и т.д.):

enum AuthToken {
    case apple(code: String, name: String)
}

Результат наружу будем передавать с помощью Observable из RxSwift:

protocol AuthProviderProtocol {
    var authResult: Observable<AuthToken> { get }

    func login()
    func logout()
}

Реализация протокола:

import AuthenticationServices
import Foundation
import RxSwift

@available(iOS 13.0, *)
class AppleAuthService: AuthProviderProtocol {

    private let authResultSubject = PublishSubject<AuthToken>()
    var authResult: Observable<AuthToken> {
        return authResultSubject.asObservable()
    }

    func login() {
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]

        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.performRequests()
    }

}

@available(iOS 13.0, *)
extension AppleAuthService: ASAuthorizationControllerDelegate {

    func authorizationController(
        controller: ASAuthorizationController,
        didCompleteWithAuthorization authorization: ASAuthorization
    ) {
        guard
            let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
            let tokenData = credential.authorizationCode,
            let token = String(data: tokenData, encoding: .utf8)
        else { return }

        let firstName = credential.fullName?.givenName
        let lastName = credential.fullName?.familyName

        authResultSubject.onNext(.apple(code: token, name: firstName + lastName))
    }

}

Нюансы, о которых нужно знать


  1. У Sign in with Apple нету функции logout в классическом понимании этого слова. Библиотека не хранит никакие данные, в отличие от других библиотек входа, поэтому нет необходимости стирать данные, полученные при логине.
  2. Sign in with Apple получает имя и фамилию пользователя только один раз при самом первом логине. У сервера нет доступа к этим данным. При последующих попытках входа вам будут приходить только authorizationCode из ASAuthorizationAppleIDCredential. Поэтому мы на клиентской стороне храним имя и фамилию пользователя до тех пор, пока регистрация на сервере не завершится успешно.
  3. Sign in with Apple позволяет пользователю подменить свой e-mail. На подмененный e-mail можно написать только с тех доменов, которые вы укажете в настройках на developer.apple.com

  4. В этой статье описано то, как мы реализовали back-end часть.

Статья получилось небольшой, но надеемся что она была полезной для вас.

Благодарим за внимание!
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 15

    0
    А почему вы не использовали identityToken?
    A JSON Web Token (JWT) used to communicate information about the identity of the user in a secure way to the app. The ID token will contain the following information: Issuer Identifier, Subject Identifier, Audience, Expiry Time and Issuance Time signed by Apple's identity service.

      +1
      Мы используем idenityToken, но на back-end части. Со стороны iOS приложения никаких телодвижений делать не нужно.
        0
        Я правильно понимаю, что вы на back-end части получаете такой же idenityToken, который генерируется в приложении?
          0
          В приложение idenityToken не генерируется, в приложении генерируется code для авторизации. IdenityToken можно получить через API Apple.
            0
            Посмотрите ASAuthorizationAppleIDCredential, там есть identityToken
              0
              Да, он есть, но мы не берем его из приложения, а back-end получает уже свой.
                0
                Соответственно, я возвращаюсь к своему изначальному вопросу. По какой причине вы не использовали identityToken, который генерируется в приложении? Почему вы приняли решение получать его именно на back-end? Это разные identityToken? Или быть может решили что это не безопасно его передавать от приложения к back-end?
                  +2
                  Я так понимаю это рекомендация Apple.
                  В плане безопасности authorizationCode лучше — он одноразовый.
                  А по-поводу identityToken — он не такой удобный как кажется… В первый раз он содержит имя и мейл, во все последующие нет (даже если ты указываешь их в requestedScopes).
                  Пока я встречал два пути решения проблемы:
                  1) Хранить локально имя и мейл до завершения регистрации на сервере, как предлагает автор статьи.
                  2) Если регистрация была прервана, при следующей попытке просить пользователя удалить ассоциацию с приложением в Settings.
                    0
                    Вы не встречали в документации где Apple высказывалась мол не используйте identityToken, а используйте authorizationCode? Хотелось бы наверняка знать.

                    Можно при следующей попытке попросить пользователя ввести любые имя и мейл. Ведь главное получить id пользователя? И он отдается всегда. И он должен быть постоянным на всех девайсах данного пользователя в конкретном приложении.
                      0
                      Напрямую не встречал, но к примеру если сравнить описания:
                      A JSON Web Token (JWT) that securely communicates information about the user to your app.

                      и
                      A short-lived token used by your app for proof of authorization when interacting with the app’s server counterpart.

                      то мне кажется понятно как Apple предполагает их использовать.

                      Можно при следующей попытке попросить пользователя ввести любые имя и мейл.

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

                        0
                        Согласен, пользователи не любят, но это скорее решение на крайний случай, который происходить будет редко. Если, например, у вас в приложении есть авторизация через VK и вы добавляете email в scope, то вы не всегда получите почту. Пользователь может запретить. В этом случае, как ни крути, надо показывать доп экран с просьбой ввести почту. И переиспользовать этот же экран для sign in with apple вполне себя оправдает.
                    +1
                    Мой коллега kurenkoff немного ошибся. Мы действительно на клиенте можем получить identityToken. Решили использовать вместо него authorizationCode по соображениям безопасности, т.к. он имеет короткое время жизни.

                    При желании, можно делать как вы говорите — получать identityToken на клиенте и кидать его на сервер. Это вопрос конкретной реализации.
                      +1
                      Понял, спасибо. Кстати identityToken тоже имеет не долгий срок жизни, около 10 минут.
        +1
        Я бы советовал добавить в Нюансы как раз реализацию logout. Если пользователь разорвал ассоциацию Apple ID с приложением в Settings (Settings/ Apple ID / Password & Security / Apple ID logins / Edit) вы должны выполнить logout в приложении / на сервере. Apple рекомендует проверять это на старте приложения с помощью ASAuthorizationAppleIDProvider().getCredentialState
          +1
          Да, вы правы, однако мы имели в виду немного другое
          Google и Facebook реализуют функцию logout() в своих библиотеках, а у Apple ее нет, так как в ней по сути нет необходимости
          А проверкой токена на случай логаута от юзера у нас занимается back-end когда мы пытаемся получить данные пользователя

        Only users with full accounts can post comments. Log in, please.