Вы можете разрешить пользователям выполнять аутентификацию в Firebase с помощью Apple ID, используя Firebase SDK для реализации сквозного процесса входа OAuth 2.0.
Подготовка
Чтобы пользователи могли входить в систему с помощью Apple, сначала настройте функцию "Войти с Apple" на сайте для разработчиков Apple, а затем включите Apple в качестве поставщика учетных данных для входа в проект Firebase.
Как присоединиться к программе Apple Developer Program
Настроить функцию "Войти через аккаунт Apple" могут только участники программы для разработчиков Apple.
Как настроить вход через аккаунт Apple
На сайте Apple Developer выполните следующие действия:
-
Свяжите сайт с приложением, как описано в первом разделе статьи Настройка входа с Apple ID для сайта. Когда появится запрос, зарегистрируйте следующий URL в качестве URL возврата:
https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
Идентификатор проекта Firebase можно найти в разделе
Настройки > Общие на консоли Firebase.После этого запишите новый идентификатор сервиса, который понадобится вам в следующем разделе.
- Создайте закрытый ключ для входа через Apple. В следующем разделе вам понадобятся новый закрытый ключ и идентификатор ключа.
-
Если вы используете какие-либо функции Firebase Authentication, которые отправляют пользователям электронные письма, в том числе вход по ссылке, подтверждение адреса электронной почты, отмену изменений в аккаунте и другие, настройте частный сервис ретрансляции электронной почты Apple и зарегистрируйте
noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com(или домен вашего шаблона электронного письма), чтобы Apple могла пересылать письма, отправленные Firebase Authentication, на анонимизированные адреса электронной почты Apple.
Как включить Apple в качестве поставщика услуг входа
- Добавьте Firebase в проект для Android.
-
Укажите цифровой отпечаток SHA-1 приложения, если вы ещё этого не сделали.
-
В консоли Firebase перейдите на вкладку
Настройки > Общие. - Прокрутите страницу вниз до карточки Ваши приложения, выберите приложение Android и добавьте цифровой отпечаток SHA-1 в поле Цифровые отпечатки сертификата SHA.
-
В консоли Firebase перейдите на вкладку
- В консоли Firebase выберите Безопасность > Аутентификация.
- На вкладке Способ входа включите поставщика для входа через 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, выполните следующие действия:
Создайте экземпляр
OAuthProvider, используя его конструктор с идентификатором поставщикаapple.com:Kotlin
val provider = OAuthProvider.newBuilder("apple.com")Java
OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");Необязательно. Укажите дополнительные области 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 области действия, если вы не укажете их.
Необязательно. Если вы хотите, чтобы экран входа в аккаунт 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");Выполните аутентификацию в 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.
Для каждого запроса на вход генерируйте случайную строку – "однократно используемый номер" (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.
Инициируйте процесс входа с аккаунтом Apple, используя библиотеку OAuth или другой метод. Обязательно включите хешированное однократно используемое число в качестве параметра в запрос.
После того как вы получите ответ от Apple, извлеките из него токен идентификатора и используйте его вместе с хешированным одноразовым кодом, чтобы создать
AuthCredential:Kotlin
val credential = OAuthProvider.newCredentialBuilder("apple.com") .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce) .build()Java
AuthCredential credential = OAuthProvider.newCredentialBuilder("apple.com") .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce) .build();Выполните аутентификацию в 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" для отзыва токенов пользователей.
Чтобы выполнить это требование, сделайте следующее:
Используйте метод
startActivityForSignInWithProvider()для входа с помощью Apple и полученияAuthResult.Получите токен доступа для поставщика Apple.
Kotlin
val oauthCredential: OAuthCredential = authResult.credential val accessToken = oauthCredential.accessTokenJava
OAuthCredential oauthCredential = (OAuthCredential) authResult.getCredential(); String accessToken = oauthCredential.getAccessToken();Отзовите токен с помощью
revokeAccessTokenAPI.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 ... } } });
- Наконец, удалите аккаунт пользователя (и все связанные с ним данные).
Дальнейшие действия
После первого входа пользователя создается новый аккаунт, связанный с учетными данными, которые он использовал (именем пользователя и паролем, номером телефона или информацией поставщика услуг аутентификации). Этот новый аккаунт хранится в проекте Firebase и позволяет идентифицировать пользователя во всех приложениях проекта независимо от того, как он вошел в аккаунт.
-
В своих приложениях вы можете получить основную информацию профиля пользователя из объекта
FirebaseUser. Подробнее о том, как управлять пользователями… В Firebase Realtime Database и Cloud Storage правилах безопасности можно получить уникальный идентификатор пользователя, выполнившего вход, из переменной
authи использовать его, чтобы контролировать, к каким данным у пользователя есть доступ.
Вы можете разрешить пользователям входить в ваше приложение, используя несколько поставщиков услуг аутентификации, связав учетные данные поставщика услуг аутентификации с существующим аккаунтом пользователя.
Чтобы выйти из аккаунта пользователя, вызовите функцию
signOut:Kotlin
Firebase.auth.signOut()
Java
FirebaseAuth.getInstance().signOut();
-