Как выполнить аутентификацию с помощью Apple на устройстве Android

Вы можете разрешить пользователям выполнять аутентификацию в Firebase с помощью Apple ID, используя Firebase SDK для реализации сквозного процесса входа OAuth 2.0.

Подготовка

Чтобы пользователи могли входить в систему с помощью Apple, сначала настройте функцию "Войти с Apple" на сайте для разработчиков Apple, а затем включите Apple в качестве поставщика учетных данных для входа в проект Firebase.

Как присоединиться к программе Apple Developer Program

Настроить функцию "Войти через аккаунт Apple" могут только участники программы для разработчиков Apple.

Как настроить вход через аккаунт Apple

На сайте Apple Developer выполните следующие действия:

  1. Свяжите сайт с приложением, как описано в первом разделе статьи Настройка входа с Apple ID для сайта. Когда появится запрос, зарегистрируйте следующий URL в качестве URL возврата:

    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler

    Идентификатор проекта Firebase можно найти в разделе Настройки > Общие на консоли Firebase.

    После этого запишите новый идентификатор сервиса, который понадобится вам в следующем разделе.

  2. Создайте закрытый ключ для входа через Apple. В следующем разделе вам понадобятся новый закрытый ключ и идентификатор ключа.
  3. Если вы используете какие-либо функции Firebase Authentication, которые отправляют пользователям электронные письма, в том числе вход по ссылке, подтверждение адреса электронной почты, отмену изменений в аккаунте и другие, настройте частный сервис ретрансляции электронной почты Apple и зарегистрируйте noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (или домен вашего шаблона электронного письма), чтобы Apple могла пересылать письма, отправленные Firebase Authentication, на анонимизированные адреса электронной почты Apple.

Как включить Apple в качестве поставщика услуг входа

  1. Добавьте Firebase в проект для Android.
  2. Укажите цифровой отпечаток SHA-1 приложения, если вы ещё этого не сделали.
    1. В консоли Firebase перейдите на вкладку Настройки > Общие.
    2. Прокрутите страницу вниз до карточки Ваши приложения, выберите приложение Android и добавьте цифровой отпечаток SHA-1 в поле Цифровые отпечатки сертификата SHA.
  3. В консоли Firebase выберите Безопасность > Аутентификация.
  4. На вкладке Способ входа включите поставщика для входа через Apple. Укажите идентификатор сервиса, созданный в предыдущем разделе. Кроме того, в разделе конфигурации потока кода OAuth укажите идентификатор команды Apple, закрытый ключ и идентификатор ключа, созданные в предыдущем разделе.

Соблюдайте требования Apple к анонимизированным данным

При входе через Apple ID пользователи могут анонимизировать свои данные, в том числе адрес электронной почты. Пользователи, которые выберут этот вариант, получат адреса электронной почты с доменом privaterelay.appleid.com. При использовании функции "Войти с Apple" в приложении вы должны соблюдать все применимые правила для разработчиков и условия использования Apple, касающиеся анонимизированных идентификаторов Apple.

В частности, перед тем как связать с анонимизированным идентификатором Apple ID любую информацию, позволяющую установить личность, вы должны получить согласие пользователя, если это необходимо. При использовании Firebase Authentication могут выполняться следующие действия:

  • Связать адрес электронной почты с анонимным Apple ID или наоборот.
  • Как связать номер телефона с анонимизированным Apple ID или наоборот
  • Связать анонимный идентификатор Apple ID с неанонимными учетными данными социальной сети (Facebook, Google и т. д.) или наоборот.

Этот список не является исчерпывающим. Чтобы убедиться, что ваше приложение соответствует требованиям Apple, ознакомьтесь с лицензионным соглашением программы для разработчиков Apple в разделе "Членство" вашего аккаунта разработчика.

Как обрабатывать процесс входа с помощью Firebase SDK

На устройствах Android проще всего аутентифицировать пользователей в Firebase с помощью их аккаунтов Apple, обрабатывая весь процесс входа с помощью Firebase Android SDK.

Чтобы реализовать процесс входа с помощью Firebase Android SDK, выполните следующие действия:

  1. Создайте экземпляр OAuthProvider, используя его конструктор с идентификатором поставщика apple.com:

    Kotlin

    val provider = OAuthProvider.newBuilder("apple.com")
    

    Java

    OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");
    
  2. Необязательно. Укажите дополнительные области OAuth 2.0, которые вы хотите запросить у поставщика услуг аутентификации.

    Kotlin

    provider.setScopes(arrayOf("email", "name"))
    

    Java

    List<String> scopes =
        new ArrayList<String>() {
          {
            add("email");
            add("name");
          }
        };
    provider.setScopes(scopes);
    

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

  3. Необязательно. Если вы хотите, чтобы экран входа в аккаунт Apple отображался не на английском языке, задайте параметр locale. Список поддерживаемых языков можно найти в документации по входу через Apple.

    Kotlin

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr")
    

    Java

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr");
    
  4. Выполните аутентификацию в Firebase, используя объект поставщика OAuth. Обратите внимание, что в отличие от других операций FirebaseAuth, эта операция возьмет на себя управление вашим интерфейсом, открыв специальную вкладку Chrome. Поэтому не ссылайтесь на Activity в OnSuccessListener и OnFailureListener, которые вы прикрепляете, поскольку они будут немедленно отсоединены, когда операция запустит интерфейс.

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

    Чтобы проверить, есть ли ожидающий результат, вызовите функцию getPendingAuthResult():

    Kotlin

    val pending = auth.pendingAuthResult
    if (pending != null) {
        pending.addOnSuccessListener { authResult ->
            Log.d(TAG, "checkPending:onSuccess:$authResult")
            // Get the user profile with authResult.getUser() and
            // authResult.getAdditionalUserInfo(), and the ID
            // token from Apple with authResult.getCredential().
        }.addOnFailureListener { e ->
            Log.w(TAG, "checkPending:onFailure", e)
        }
    } else {
        Log.d(TAG, "pending: null")
    }
    

    Java

    mAuth = FirebaseAuth.getInstance();
    Task<AuthResult> pending = mAuth.getPendingAuthResult();
    if (pending != null) {
        pending.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
            @Override
            public void onSuccess(AuthResult authResult) {
                Log.d(TAG, "checkPending:onSuccess:" + authResult);
                // Get the user profile with authResult.getUser() and
                // authResult.getAdditionalUserInfo(), and the ID
                // token from Apple with authResult.getCredential().
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.w(TAG, "checkPending:onFailure", e);
            }
        });
    } else {
        Log.d(TAG, "pending: null");
    }
    

    Если нет ожидающих результатов, запустите процесс входа, вызвав функцию startActivityForSignInWithProvider():

    Kotlin

    auth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener { authResult ->
                // Sign-in successful!
                Log.d(TAG, "activitySignIn:onSuccess:${authResult.user}")
                val user = authResult.user
                // ...
            }
            .addOnFailureListener { e ->
                Log.w(TAG, "activitySignIn:onFailure", e)
            }
    

    Java

    mAuth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener(
                    new OnSuccessListener<AuthResult>() {
                        @Override
                        public void onSuccess(AuthResult authResult) {
                            // Sign-in successful!
                            Log.d(TAG, "activitySignIn:onSuccess:" + authResult.getUser());
                            FirebaseUser user = authResult.getUser();
                            // ...
                        }
                    })
            .addOnFailureListener(
                    new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Log.w(TAG, "activitySignIn:onFailure", e);
                        }
                    });
    

    В отличие от других поставщиков, поддерживаемых Firebase Auth, Apple не предоставляет URL фотографии.

    Если пользователь не хочет делиться своим адресом электронной почты с приложением, Apple предоставляет ему уникальный адрес (в формате xyz@privaterelay.appleid.com), который передается приложению. Если вы настроили службу ретрансляции частных электронных адресов, Apple пересылает письма, отправленные на анонимизированный адрес, на настоящий адрес пользователя.

    Apple передает приложениям информацию о пользователе, например отображаемое имя, только при первом входе в аккаунт. Обычно Firebase сохраняет отображаемое имя при первом входе пользователя с помощью Apple ID. Получить его можно с помощью getCurrentUser().getDisplayName(). Однако если вы ранее использовали Apple для входа пользователя в приложение без Firebase, Apple не предоставит Firebase отображаемое имя пользователя.

Повторная аутентификация и связывание аккаунтов

Аналогичный шаблон можно использовать с startActivityForReauthenticateWithProvider(), чтобы получать новые учетные данные для конфиденциальных операций, требующих недавнего входа в аккаунт:

Kotlin

// The user is already signed-in.
val firebaseUser = auth.getCurrentUser()

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener( authResult -> {
        // User is re-authenticated with fresh tokens and
        // should be able to perform sensitive operations
        // like account deletion and email or password
        // update.
    })
    .addOnFailureListener( e -> {
        // Handle failure.
    })

Java

// The user is already signed-in.
FirebaseUser firebaseUser = mAuth.getCurrentUser();

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener(
        new OnSuccessListener<AuthResult>() {
          @Override
          public void onSuccess(AuthResult authResult) {
            // User is re-authenticated with fresh tokens and
            // should be able to perform sensitive operations
            // like account deletion and email or password
            // update.
          }
        })
    .addOnFailureListener(
        new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            // Handle failure.
          }
        });

Кроме того, с помощью linkWithCredential() можно связать разных поставщиков идентификационной информации с существующими аккаунтами.

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

Например, чтобы связать аккаунт Facebook с текущим аккаунтом Firebase, используйте токен доступа, полученный при входе пользователя в Facebook:

Kotlin

// Initialize a Facebook credential with a Facebook access token.
val credential = FacebookAuthProvider.getCredential(token.getToken())

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, task -> {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      });

Java

// Initialize a Facebook credential with a Facebook access token.
AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
      @Override
      public void onComplete(@NonNull Task<AuthResult> task) {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      }
    });

Способ для опытных пользователей: обработка процесса входа вручную

Вы также можете выполнить аутентификацию в Firebase с помощью аккаунта Apple, обработав процесс входа с помощью JS SDK для входа через Apple, вручную создав поток OAuth или используя библиотеку OAuth, например AppAuth.

  1. Для каждого запроса на вход генерируйте случайную строку – "однократно используемый номер" (nonce), – которая позволит убедиться, что полученный идентификатор был предоставлен в ответ на запрос аутентификации вашего приложения. Этот шаг важен для предотвращения атак с повторным воспроизведением.

    На устройстве Android можно сгенерировать криптографически защищенное одноразовое число с помощью SecureRandom, как показано в следующем примере:

    Kotlin

    private fun generateNonce(length: Int): String {
        val generator = SecureRandom()
    
        val charsetDecoder = StandardCharsets.US_ASCII.newDecoder()
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE)
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE)
    
        val bytes = ByteArray(length)
        val inBuffer = ByteBuffer.wrap(bytes)
        val outBuffer = CharBuffer.allocate(length)
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes)
            inBuffer.rewind()
            charsetDecoder.reset()
            charsetDecoder.decode(inBuffer, outBuffer, false)
        }
        outBuffer.flip()
        return outBuffer.toString()
    }
    

    Java

    private String generateNonce(int length) {
        SecureRandom generator = new SecureRandom();
    
        CharsetDecoder charsetDecoder = StandardCharsets.US_ASCII.newDecoder();
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE);
    
        byte[] bytes = new byte[length];
        ByteBuffer inBuffer = ByteBuffer.wrap(bytes);
        CharBuffer outBuffer = CharBuffer.allocate(length);
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes);
            inBuffer.rewind();
            charsetDecoder.reset();
            charsetDecoder.decode(inBuffer, outBuffer, false);
        }
        outBuffer.flip();
        return outBuffer.toString();
    }
    

    Затем получите хеш SHA246 nonce в виде шестнадцатеричной строки:

    Kotlin

    private fun sha256(s: String): String {
        val md = MessageDigest.getInstance("SHA-256")
        val digest = md.digest(s.toByteArray())
        val hash = StringBuilder()
        for (c in digest) {
            hash.append(String.format("%02x", c))
        }
        return hash.toString()
    }
    

    Java

    private String sha256(String s) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digest = md.digest(s.getBytes());
        StringBuilder hash = new StringBuilder();
        for (byte c: digest) {
            hash.append(String.format("%02x", c));
        }
        return hash.toString();
    }
    

    Вы отправите хеш SHA256 одноразового кода с запросом на вход, и Apple передаст его без изменений в ответе. Firebase проверяет ответ, хешируя исходное одноразовое число и сравнивая его со значением, переданным Apple.

  2. Инициируйте процесс входа с аккаунтом Apple, используя библиотеку OAuth или другой метод. Обязательно включите хешированное однократно используемое число в качестве параметра в запрос.

  3. После того как вы получите ответ от Apple, извлеките из него токен идентификатора и используйте его вместе с хешированным одноразовым кодом, чтобы создать AuthCredential:

    Kotlin

    val credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build()
    

    Java

    AuthCredential credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build();
    
  4. Выполните аутентификацию в Firebase с помощью учетных данных Firebase:

    Kotlin

    auth.signInWithCredential(credential)
          .addOnCompleteListener(this) { task ->
              if (task.isSuccessful) {
                // User successfully signed in with Apple ID token.
                // ...
              }
          }
    

    Java

    mAuth.signInWithCredential(credential)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
              // User successfully signed in with Apple ID token.
              // ...
            }
          }
        });
    

Если вызов signInWithCredential выполнен успешно, вы можете использовать метод getCurrentUser, чтобы получить данные аккаунта пользователя.

Отзыв токена

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

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

Чтобы выполнить это требование, сделайте следующее:

  1. Используйте метод startActivityForSignInWithProvider() для входа с помощью Apple и получения AuthResult.

  2. Получите токен доступа для поставщика Apple.

    Kotlin

    val oauthCredential: OAuthCredential =  authResult.credential
    val accessToken = oauthCredential.accessToken
    

    Java

    OAuthCredential oauthCredential = (OAuthCredential) authResult.getCredential();
    String accessToken = oauthCredential.getAccessToken();
    
  3. Отзовите токен с помощью revokeAccessToken API.

    Kotlin

    mAuth.revokeAccessToken(accessToken)
      .addOnCompleteListener(this) { task ->
        if (task.isSuccessful) {
          // Access token successfully revoked
          // for the user ...
        }
    }
    

    Java

    mAuth.revokeAccessToken(accessToken)
        .addOnCompleteListener(this, new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
              if (task.isSuccessful()) {
                // Access token successfully revoked
                // for the user ...
              }
            }
      });
    
  1. Наконец, удалите аккаунт пользователя (и все связанные с ним данные).

    Дальнейшие действия

    После первого входа пользователя создается новый аккаунт, связанный с учетными данными, которые он использовал (именем пользователя и паролем, номером телефона или информацией поставщика услуг аутентификации). Этот новый аккаунт хранится в проекте Firebase и позволяет идентифицировать пользователя во всех приложениях проекта независимо от того, как он вошел в аккаунт.

    • В своих приложениях вы можете получить основную информацию профиля пользователя из объекта FirebaseUser. Подробнее о том, как управлять пользователями…

    • В Firebase Realtime Database и Cloud Storage правилах безопасности можно получить уникальный идентификатор пользователя, выполнившего вход, из переменной auth и использовать его, чтобы контролировать, к каким данным у пользователя есть доступ.

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

    Чтобы выйти из аккаунта пользователя, вызовите функцию signOut:

    Kotlin

    Firebase.auth.signOut()

    Java

    FirebaseAuth.getInstance().signOut();