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

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

Подготовка

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

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

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

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

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

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

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

        О том, как получить цифровой отпечаток SHA приложения, можно прочитать в разделе об аутентификации клиентов.

      3. Настройте параметры поставщика "Войти с Apple":
        1. В консоли Firebase выберите Безопасность > Аутентификация.
        2. На вкладке Способ входа нажмите на поставщика услуг входа Apple.
        3. Укажите идентификатор сервиса, созданный в предыдущем разделе. Кроме того, в разделе конфигурации потока кода 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::auth::Auth

Класс Auth – это шлюз для всех вызовов API.
  1. Добавьте файлы заголовков Auth и App:
    #include <cassert>
    #include <cstdint>
    
    #include "firebase/app.h"
    #include "firebase/auth.h"
  2. В коде инициализации создайте класс firebase::App.
    #if defined(__ANDROID__)
      firebase::App* app =
          firebase::App::Create(firebase::AppOptions(), my_jni_env, my_activity);
    #else
      firebase::App* app = firebase::App::Create(firebase::AppOptions());
    #endif  // defined(__ANDROID__)
  3. Получите класс firebase::auth::Auth для вашего firebase::App. Между App и Auth существует однозначное соответствие.
    firebase::auth::Auth* auth = firebase::auth::Auth::GetAuth(app);

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

Процесс входа с аккаунтом Apple различается на платформах Apple и Android.

На платформах Apple

Выполните аутентификацию пользователей с помощью Firebase через Apple Sign In Objective-C SDK, вызванный из кода C++.

  1. Для каждого запроса на вход генерируйте случайную строку – "однократно используемый номер" (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--;
            }
          }
        }
      }
    
    

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

  2. Запустите процесс входа через Apple, включив в запрос хеш SHA256 для nonce и класс делегата, который будет обрабатывать ответ Apple (см. следующий шаг):

      - (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];
      }
    
      - (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;
      }
    
  3. Обработайте ответ Apple в реализации ASAuthorizationControllerDelegate. Если вход выполнен успешно, используйте токен идентификатора из ответа Apple с хешированным одноразовым кодом для аутентификации в Firebase:

      - (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);
          }
        }
    
  4. Используйте полученную строку токена и исходное одноразовое число, чтобы создать Firebase Credential и войти в Firebase.

    firebase::auth::OAuthProvider::GetCredential(
            /*provider_id=*/"apple.com", token, nonce,
            /*access_token=*/nullptr);
    
    firebase::Future<firebase::auth::AuthResult> result =
        auth->SignInAndRetrieveDataWithCredential(credential);
    
  5. Аналогичный шаблон можно использовать с функцией Reauthenticate, которая позволяет получать новые учетные данные для выполнения конфиденциальных операций, требующих недавнего входа в систему.

    firebase::Future<firebase::auth::AuthResult> result =
        user->Reauthenticate(credential);
    
  6. По такому же принципу можно связать аккаунт с функцией "Войти с Apple". Однако при попытке связать аккаунт Apple с аккаунтом Firebase, который уже связан с другим аккаунтом Apple, может возникнуть ошибка. В этом случае в будущем будет возвращен статус kAuthErrorCredentialAlreadyInUse, а в AuthResult может содержаться действительный credential. Эти учетные данные можно использовать для входа в аккаунт, связанный с Apple, через SignInAndRetrieveDataWithCredential без необходимости генерировать ещё один токен и одноразовый код для входа через Apple.

    firebase::Future<firebase::auth::AuthResult> link_result =
        auth->current_user().LinkWithCredential(credential);
    
    // To keep example simple, wait on the current thread until call completes.
    while (link_result.status() == firebase::kFutureStatusPending) {
      Wait(100);
    }
    
    // Determine the result of the link attempt
    if (link_result.error() == firebase::auth::kAuthErrorNone) {
      // user linked correctly.
    } else if (link_result.error() ==
                   firebase::auth::kAuthErrorCredentialAlreadyInUse &&
               link_result.result()
                   ->additional_user_info.updated_credential.is_valid()) {
      // Sign In with the new credential
      firebase::Future<firebase::auth::AuthResult> result =
          auth->SignInAndRetrieveDataWithCredential(
              link_result.result()->additional_user_info.updated_credential);
    } else {
      // Another link error occurred.
    }

На устройстве Android

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

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

  1. Создайте экземпляр FederatedOAuthProviderData, настроенный с идентификатором поставщика, подходящим для Apple.

    firebase::auth::FederatedOAuthProviderData provider_data("apple.com");
    
  2. Необязательно. Укажите дополнительные области действия OAuth 2.0, которые вы хотите запросить у поставщика услуг аутентификации.

    provider_data.scopes.push_back("email");
    provider_data.scopes.push_back("name");
    
  3. Необязательно. Если вы хотите, чтобы экран входа в аккаунт Apple отображался не на английском языке, задайте параметр locale. Список поддерживаемых языков можно найти в документации по входу через Apple.

    // Localize to French.
    provider_data.custom_parameters["language"] = "fr";
    ```
    
  4. После того как вы настроите данные поставщика, используйте их, чтобы создать FederatedOAuthProvider.

    // Construct a FederatedOAuthProvider for use in Auth methods.
    firebase::auth::FederatedOAuthProvider provider(provider_data);
    
  5. Выполните аутентификацию в Firebase с помощью объекта поставщика аутентификации. Обратите внимание, что в отличие от других операций FirebaseAuth эта операция перехватывает управление интерфейсом, показывая веб-представление, в котором пользователь может ввести свои учетные данные.

    Чтобы начать процесс входа, вызовите signInWithProvider:

    firebase::Future<firebase::auth::AuthResult> result =
      auth->SignInWithProvider(provider_data);
    

    Затем приложение может подождать или зарегистрировать обратный вызов в Future.

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

    firebase::Future<firebase::auth::AuthResult> result =
      user.ReauthenticateWithProvider(provider_data);
    

    Затем приложение может подождать или зарегистрировать обратный вызов в объекте Future.

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

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

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

    // Initialize a Facebook credential with a Facebook access token.
    AuthCredential credential =
        firebase::auth::FacebookAuthProvider.getCredential(token);
    
    // Assuming the current user is an Apple user linking a Facebook provider.
    firebase::Future<firebase::auth::AuthResult> result =
        auth.current_user().LinkWithCredential(credential);
    

Как войти в аккаунт с помощью приложения "Заметки" от Apple

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

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

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

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

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

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

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