Вы можете позволить своим пользователям проходить аутентификацию в Firebase, используя свой Apple ID, используя Firebase SDK для выполнения сквозного процесса входа OAuth 2.0.
Прежде чем вы начнете
Чтобы входить в систему с помощью Apple, сначала настройте вход с помощью Apple на сайте разработчиков Apple, а затем включите Apple в качестве поставщика входа для вашего проекта Firebase.
Присоединяйтесь к программе Apple для разработчиков
Вход с помощью Apple может быть настроен только участниками программы Apple Developer Program .
Настройка входа с помощью Apple
- Включите «Вход с Apple» для своего приложения на странице « Сертификаты, идентификаторы и профили » сайта разработчиков Apple.
- Если вы используете какие-либо функции Firebase Authentication, которые отправляют электронные письма пользователям, включая вход по ссылке электронной почты, проверку адреса электронной почты, отзыв изменения учетной записи и другие, настройте службу ретрансляции частной электронной почты Apple и зарегистрируйтесь
noreply@ YOUR_FIREBASE_PROJECT_ID .firebaseapp.com
(или ваш настроенный домен шаблона электронной почты), чтобы Apple могла ретранслировать электронные письма, отправленные с помощью Firebase Authentication, на анонимные адреса электронной почты Apple.
Включить Apple в качестве поставщика услуг входа
- Добавьте Firebase в свой проект Apple . Обязательно зарегистрируйте идентификатор пакета вашего приложения при настройке приложения в консоли Firebase.
- В консоли Firebase откройте раздел Auth . На вкладке Способ входа включите поставщика Apple . Если вы используете «Войти через Apple» только в приложении, вы можете оставить поля «Идентификатор службы», «Идентификатор команды Apple», «Закрытый ключ» и «Идентификатор ключа» пустыми.
Соблюдайте требования Apple к анонимным данным
Войти с Apple дает пользователям возможность анонимизировать свои данные, включая адрес электронной почты, при входе в систему. Пользователи, которые выбирают этот вариант, имеют адреса электронной почты с доменом privaterelay.appleid.com
. Когда вы используете «Войти через Apple» в своем приложении, вы должны соблюдать все применимые политики или условия Apple для разработчиков в отношении этих анонимных идентификаторов Apple ID.
Это включает в себя получение любого необходимого согласия пользователя, прежде чем вы свяжете какую-либо личную информацию, непосредственно идентифицирующую личность, с анонимным Apple ID. При использовании Firebase Authentication это может включать следующие действия:
- Свяжите адрес электронной почты с анонимным Apple ID или наоборот.
- Привязать номер телефона к анонимному Apple ID или наоборот
- Свяжите неанонимные социальные учетные данные (Facebook, Google и т. д.) с анонимным Apple ID или наоборот.
Приведенный выше список не является исчерпывающим. См. Лицензионное соглашение программы Apple Developer Program в разделе «Членство» вашей учетной записи разработчика, чтобы убедиться, что ваше приложение соответствует требованиям Apple.
Войдите с помощью Apple и выполните аутентификацию с помощью Firebase.
Чтобы пройти аутентификацию с помощью учетной записи Apple, сначала войдите в свою учетную запись Apple с помощью Apple AuthenticationServices
framework , а затем используйте токен ID из ответа Apple для создания объекта Firebase AuthCredential
:
Для каждого запроса на вход создавайте случайную строку — «одноразовый номер», — которую вы будете использовать, чтобы убедиться, что токен идентификатора, который вы получаете, был предоставлен именно в ответ на запрос проверки подлинности вашего приложения. Этот шаг важен для предотвращения повторных атак.
Вы можете сгенерировать криптографически безопасный одноразовый номер с помощью
SecRandomCopyBytes(_:_:_)
, как в следующем примере:Быстрый
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce private func randomNonceString(length: Int = 32) -> String { precondition(length > 0) let charset: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._") var result = "" var remainingLength = length while remainingLength > 0 { let randoms: [UInt8] = (0 ..< 16).map { _ in var random: UInt8 = 0 let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random) if errorCode != errSecSuccess { fatalError( "Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)" ) } return random } randoms.forEach { random in if remainingLength == 0 { return } if random < charset.count { result.append(charset[Int(random)]) remainingLength -= 1 } } } return result }
Цель-C
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce - (NSString *)randomNonce:(NSInteger)length { NSAssert(length > 0, @"Expected nonce to have positive length"); NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._"; NSMutableString *result = [NSMutableString string]; NSInteger remainingLength = length; while (remainingLength > 0) { NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16]; for (NSInteger i = 0; i < 16; i++) { uint8_t random = 0; int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random); NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode); [randoms addObject:@(random)]; } for (NSNumber *random in randoms) { if (remainingLength == 0) { break; } if (random.unsignedIntValue < characterSet.length) { unichar character = [characterSet characterAtIndex:random.unsignedIntValue]; [result appendFormat:@"%C", character]; remainingLength--; } } } return [result copy]; }
Вы отправите хэш SHA256 одноразового номера вместе с запросом на вход, который Apple передаст без изменений в ответе. Firebase проверяет ответ, хэшируя исходный одноразовый номер и сравнивая его со значением, переданным Apple.
Быстрый
@available(iOS 13, *) private func sha256(_ input: String) -> String { let inputData = Data(input.utf8) let hashedData = SHA256.hash(data: inputData) let hashString = hashedData.compactMap { String(format: "%02x", $0) }.joined() return hashString }
Цель-C
- (NSString *)stringBySha256HashingString:(NSString *)input { const char *string = [input UTF8String]; unsigned char result[CC_SHA256_DIGEST_LENGTH]; CC_SHA256(string, (CC_LONG)strlen(string), result); NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { [hashed appendFormat:@"%02x", result[i]]; } return hashed; }
Запустите процесс входа Apple, включив в свой запрос хэш SHA256 одноразового номера и класс делегата, который будет обрабатывать ответ Apple (см. следующий шаг):
Быстрый
import CryptoKit // Unhashed nonce. fileprivate var currentNonce: String? @available(iOS 13, *) func startSignInWithAppleFlow() { let nonce = randomNonceString() currentNonce = nonce let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() request.requestedScopes = [.fullName, .email] request.nonce = sha256(nonce) let authorizationController = ASAuthorizationController(authorizationRequests: [request]) authorizationController.delegate = self authorizationController.presentationContextProvider = self authorizationController.performRequests() }
Цель-C
@import CommonCrypto; - (void)startSignInWithAppleFlow { NSString *nonce = [self randomNonce:32]; self.currentNonce = nonce; ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init]; ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest]; request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail]; request.nonce = [self stringBySha256HashingString:nonce]; ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]]; authorizationController.delegate = self; authorizationController.presentationContextProvider = self; [authorizationController performRequests]; }
Обработайте ответ Apple в своей реализации
ASAuthorizationControllerDelegate
. Если вход прошел успешно, используйте токен ID из ответа Apple с нехешированным одноразовым номером для аутентификации в Firebase:Быстрый
@available(iOS 13.0, *) extension MainViewController: ASAuthorizationControllerDelegate { func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { guard let nonce = currentNonce else { fatalError("Invalid state: A login callback was received, but no login request was sent.") } guard let appleIDToken = appleIDCredential.identityToken else { print("Unable to fetch identity token") return } guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { print("Unable to serialize token string from data: \(appleIDToken.debugDescription)") return } // Initialize a Firebase credential. let credential = OAuthProvider.credential(withProviderID: "apple.com", IDToken: idTokenString, rawNonce: nonce) // Sign in with Firebase. Auth.auth().signIn(with: credential) { (authResult, error) in if error { // Error. If error.code == .MissingOrInvalidNonce, make sure // you're sending the SHA256-hashed nonce as a hex string with // your request to Apple. print(error.localizedDescription) return } // User is signed in to Firebase with Apple. // ... } } } func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { // Handle error. print("Sign in with Apple errored: \(error)") } }
Цель-C
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) { if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) { ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential; NSString *rawNonce = self.currentNonce; NSAssert(rawNonce != nil, @"Invalid state: A login callback was received, but no login request was sent."); if (appleIDCredential.identityToken == nil) { NSLog(@"Unable to fetch identity token."); return; } NSString *idToken = [[NSString alloc] initWithData:appleIDCredential.identityToken encoding:NSUTF8StringEncoding]; if (idToken == nil) { NSLog(@"Unable to serialize id token from data: %@", appleIDCredential.identityToken); } // Initialize a Firebase credential. FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com" IDToken:idToken rawNonce:rawNonce]; // Sign in with Firebase. [[FIRAuth auth] signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (error != nil) { // Error. If error.code == FIRAuthErrorCodeMissingOrInvalidNonce, // make sure you're sending the SHA256-hashed nonce as a hex string // with your request to Apple. return; } // Sign-in succeeded! }]; } } - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) { NSLog(@"Sign in with Apple errored: %@", error); }
В отличие от других поставщиков, поддерживаемых Firebase Auth, Apple не предоставляет URL-адрес фотографии.
Кроме того, когда пользователь решает не делиться своей электронной почтой с приложением, Apple предоставляет уникальный адрес электронной почты для этого пользователя (в форме xyz@privaterelay.appleid.com
), который он использует совместно с вашим приложением. Если вы настроили частную службу ретрансляции электронной почты, Apple перенаправляет электронные письма, отправленные на анонимный адрес, на реальный адрес электронной почты пользователя.
Apple передает информацию о пользователе, такую как отображаемое имя, приложениям только при первом входе пользователя в систему. Обычно Firebase сохраняет отображаемое имя при первом входе пользователя в Apple, которое вы можете получить с помощью Auth.auth().currentUser.displayName
. Однако если вы ранее использовали Apple для входа пользователя в приложение без использования Firebase, Apple не предоставит Firebase отображаемое имя пользователя.
Повторная аутентификация и привязка аккаунта
Тот же шаблон можно использовать с reauthenticateWithCredential()
, который вы можете использовать для получения новых учетных данных для конфиденциальных операций, требующих недавнего входа в систему:
Быстрый
// Initialize a fresh Apple credential with Firebase.
let credential = OAuthProvider.credential(
withProviderID: "apple.com",
IDToken: appleIdToken,
rawNonce: rawNonce
)
// Reauthenticate current Apple user with fresh Apple credential.
Auth.auth().currentUser.reauthenticate(with: credential) { (authResult, error) in
guard error != nil else { return }
// Apple user successfully re-authenticated.
// ...
}
Цель-C
FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com",
IDToken:appleIdToken,
rawNonce:rawNonce];
[[FIRAuth auth].currentUser
reauthenticateWithCredential:credential
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
if (error) {
// Handle error.
}
// Apple user successfully re-authenticated.
// ...
}];
Кроме того, вы можете использовать linkWithCredential()
для привязки различных поставщиков удостоверений к существующим учетным записям.
Обратите внимание, что Apple требует, чтобы вы получили явное согласие пользователей, прежде чем связать их учетные записи Apple с другими данными.
Вход с помощью Apple не позволит повторно использовать учетные данные для привязки к существующей учетной записи. Если вы хотите связать учетные данные для входа с помощью Apple с другой учетной записью, вы должны сначала попытаться связать учетные записи, используя старые учетные данные для входа с Apple, а затем изучить возвращенную ошибку, чтобы найти новые учетные данные. Новые учетные данные будут расположены в словаре userInfo
ошибки, и доступ к ним можно будет получить с помощью ключа AuthErrorUserInfoUpdatedCredentialKey
.
Например, чтобы связать учетную запись Facebook с текущей учетной записью Firebase, используйте токен доступа, полученный при входе пользователя в Facebook:
Быстрый
// Initialize a Facebook credential with Firebase.
let credential = FacebookAuthProvider.credential(
withAccessToken: AccessToken.current!.tokenString
)
// Assuming the current user is an Apple user linking a Facebook provider.
Auth.auth().currentUser.link(with: credential) { (authResult, error) in
// Facebook credential is linked to the current Apple user.
// The user can now sign in with Facebook or Apple to the same Firebase
// account.
// ...
}
Цель-C
// Initialize a Facebook credential with Firebase.
FacebookAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:accessToken];
// Assuming the current user is an Apple user linking a Facebook provider.
[FIRAuth.auth linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
// Facebook credential is linked to the current Apple user.
// The user can now sign in with Facebook or Apple to the same Firebase
// account.
// ...
}];
Следующие шаги
После того, как пользователь входит в систему в первый раз, создается новая учетная запись пользователя и связывается с учетными данными, т. е. с именем пользователя и паролем, номером телефона или информацией о поставщике проверки подлинности, с которыми пользователь вошел в систему. Эта новая учетная запись хранится как часть вашего проекта Firebase и может использоваться для идентификации пользователя во всех приложениях вашего проекта, независимо от того, как пользователь входит в систему.
В своих приложениях вы можете получить основную информацию о профиле пользователя из объекта
User
. См. Управление пользователями .В правилах безопасности базы данных Firebase Realtime и облачного хранилища вы можете получить уникальный идентификатор пользователя, вошедшего в систему, из переменной
auth
и использовать его для управления тем, к каким данным пользователь может получить доступ.
Вы можете разрешить пользователям входить в ваше приложение с помощью нескольких поставщиков проверки подлинности, связав учетные данные поставщика проверки подлинности с существующей учетной записью пользователя.
Чтобы выйти из системы, вызовите signOut:
.
Быстрый
let firebaseAuth = Auth.auth() do { try firebaseAuth.signOut() } catch let signOutError as NSError { print("Error signing out: %@", signOutError) }
Цель-C
NSError *signOutError; BOOL status = [[FIRAuth auth] signOut:&signOutError]; if (!status) { NSLog(@"Error signing out: %@", signOutError); return; }
Вы также можете добавить код обработки ошибок для всего диапазона ошибок аутентификации. См. Обработка ошибок .