Аутентификация с помощью Apple на Android

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

Прежде чем начать

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

Присоединяйтесь к программе для разработчиков Apple!

Настроить функцию «Вход с помощью Apple» могут только участники программы Apple Developer Program .

Настройка входа в систему с помощью Apple

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

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

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

    Идентификатор вашего проекта Firebase можно получить на странице настроек консоли Firebase .

    После завершения запишите свой новый идентификатор службы (Service ID), который понадобится вам в следующем разделе.

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

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

  1. Добавьте Firebase в свой Android-проект . Обязательно зарегистрируйте подпись SHA-1 вашего приложения при настройке приложения в консоли Firebase .
  2. В консоли Firebase откройте раздел «Аутентификация» . На вкладке «Метод входа» включите поставщика Apple . Укажите идентификатор службы, созданный вами в предыдущем разделе. Также в разделе конфигурации потока кода OAuth укажите свой идентификатор команды Apple, а также закрытый ключ и идентификатор ключа, созданные вами в предыдущем разделе.

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

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

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

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

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

Обрабатывайте процесс авторизации с помощью Firebase SDK.

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

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

  1. Создайте экземпляр OAuthProvider , используя его Builder с идентификатором поставщика 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, и его можно получить с помощью 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», — которую вы будете использовать, чтобы убедиться, что полученный вами токен идентификации был предоставлен именно в ответ на запрос аутентификации вашего приложения. Этот шаг важен для предотвращения атак повторного воспроизведения.

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

    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 одноразового числа (nonce), который Apple передаст в ответе без изменений. Firebase проверяет ответ, хешируя исходное значение nonce и сравнивая его со значением, переданным Apple.

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

  3. После получения ответа от Apple, извлеките из ответа токен ID и используйте его вместе с нехешированным nonce для создания объекта 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 для получения данных учетной записи пользователя.

Отзыв токена

Компания Apple требует, чтобы приложения, поддерживающие создание учетных записей, позволяли пользователям инициировать удаление своей учетной записи внутри приложения, как описано в рекомендациях по проверке приложений в App Store.

Кроме того, приложения, поддерживающие вход через Apple Sign in with Apple, должны использовать REST API Sign in with 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. Отзовите токен, используя API revokeAccessToken .

    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();