Apple と C++ を使用して認証する

Firebase SDK を使用してエンドツーエンドの OAuth 2.0 ログインフローを実行することで、ユーザーが Firebase での認証に Apple ID を使用できるようになります。

始める前に

Apple を使用してユーザーをログインさせるには、まず Apple のデベロッパー サイトで「Apple でサインイン」を構成してから、Firebase プロジェクトのログイン プロバイダとして Apple を有効にします。

Apple Developer Program に参加する

「Apple でサインイン」は Apple Developer Program のメンバーのみが構成できます。

「Apple でサインイン」を構成する

Firebase プロジェクトで Apple サインインを有効にし、適切に構成する必要があります。構成は Android プラットフォームと iOS プラットフォームで異なります。手順を進める前に、iOS または Android ガイドの「Apple でサインインを構成する」のセクションに従ってください。

Apple をログイン プロバイダとして有効にする

  1. Firebase コンソールで [Authentication] セクションを開きます。[ログイン方法] タブで、[Apple] プロバイダを有効にします。
  2. Apple ログインのプロバイダ設定を構成します。
    1. iOS のみでアプリをデプロイする場合は、[サービス ID]、[Apple チーム ID]、[秘密鍵]、[鍵 ID] のフィールドを空欄にしておけます。
    2. Android デバイスでのサポート:
      1. Firebase を Android プロジェクトに追加します。Firebase コンソールでアプリを設定する際は、必ずアプリの SHA-1 署名を登録してください。
      2. Firebase コンソールで [Authentication] セクションを開きます。[ログイン方法] タブで、[Apple] プロバイダを有効にします。前のセクションで作成したサービス ID を指定します。また、[OAuth コードフローの構成] セクションで、Apple チーム ID、前のセクションで作成した秘密鍵と鍵 ID を指定します。

Apple の匿名化データの要件を遵守する

「Apple でサインイン」には、ユーザーがログイン時に、メールアドレスを含む自分のデータを匿名化できるオプションがあります。このオプションを選択したユーザーは、ドメイン privaterelay.appleid.com のメールアドレスが作成されます。アプリで「Apple でサインイン」を使用する場合は、これらの匿名化された Apple ID に関して、Apple が定めるデベロッパー ポリシーと利用規約を遵守する必要があります。

これには、本人を直接特定できる個人情報を、匿名化された Apple ID に関連付ける前に、必要なユーザーの同意を得ることも含まれます。Firebase Authentication を使用する場合、この関連付けには、次のアクションが該当することがあります。

  • 匿名化された Apple ID にメールアドレスをリンク(またはその逆方向にリンク)する。
  • 匿名化された Apple ID に電話番号をリンク(またはその逆方向にリンク)する。
  • 匿名化された Apple ID に匿名ではないソーシャル認証情報(Facebook、Google など)をリンク(またはその逆方向にリンク)する。

上記のリストはすべてを網羅しているわけではありません。アプリが Apple の要件を満たしていることを確認するには、デベロッパー アカウントの [Membership] セクションにある Apple Developer Program License Agreement をご覧ください。

firebase::auth::Auth クラスへのアクセス

すべての API 呼び出しは Auth クラスを使用して行われます。
  1. Auth ヘッダー ファイルと App ヘッダー ファイルを追加します。
    #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::Appfirebase::auth::Auth クラスを取得します。AppAuth は、1 対 1 で対応しています。
    firebase::auth::Auth* auth = firebase::auth::Auth::GetAuth(app);
    

Firebase SDK を使用したログインフローの処理

「Apple でサインイン」を使用してログインするプロセスは、iOS と Android のプラット フォームによって異なります。

iOS 向け

C ++ コードから呼び出した Apple ログインの Objective-C SDK を使用して、Firebase でユーザーを認証します。

  1. ログイン リクエストごとにランダムな文字列「ナンス」を生成します。ナンスは、取得した ID トークンが、当該アプリの認証リクエストへのレスポンスとして付与されたことを確認するために使用します。この手順は、リプレイ攻撃の防止に重要です。

      - (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 ハッシュと、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. ASAuthorizationControllerDelegate の実装で Apple のレスポンスを処理します。ログインが正常に完了したら、Apple のレスポンスの ID トークンと、ハッシュ化していないナンスを組み合わせて使用し、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 認証情報を作成し、Firebase にログインします。

    firebase::auth::OAuthProvider::GetCredential(
            /*provider_id=*/"apple.com", token, nonce,
            /*access_token=*/nullptr);
    
    firebase::Future<firebase::auth::User*> result =
        auth->SignInWithCredential(credential);
    
  5. 同じパターンを Reauthenticate でも使用できます。これは、ログインしてから短時間のうちに行うべき機密性の高い操作のために、最新の認証情報を取得するのに使われます。

    firebase::Future<firebase::auth::SignInResult> result =
        user->Reauthenticate(credential);
    
  6. 同じパターンを使用して、アカウントを Apple サインインにリンクできます。ただし、リンクしようとしている Apple アカウントに既存の Firebase アカウントがすでにリンクされている場合は、エラーが発生することがあります。この場合、future はステータス kAuthErrorCredentialAlreadyInUse を返します。また、SignInResult の UserInfo オブジェクトには有効な updated_credential が含まれる場合があります。この認証情報を使用すると、別の Apple サインイン トークンやノンスを生成することなく、SignInWithCredential で Apple にリンクされたアカウントにログインできます。

    updated_credentialSignInResult.UserInfo オブジェクトのメンバーであるため、このオペレーションには LinkAndRetrieveDataWithCredential を使用して認証情報を含める必要があります。

    firebase::Future<firebase::auth::SignInResult> link_result =
        auth->current_user()->LinkAndRetrieveDataWithCredential(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()->info.updated_credential.is_valid()) {
      // Sign In with the new credential
      firebase::Future<firebase::auth::User*> result = auth->SignInWithCredential(
          link_result.result()->info.updated_credential);
    } else {
      // Another link error occurred.
    }
    

Android

Android では、Firebase SDK を使用してウェブベースの汎用 OAuth ログインをアプリに統合し、エンドツー エンドのログインフローを実行して、Firebase でユーザーを認証します。

Firebase SDK でログインフローを処理する手順は次のとおりです。

  1. Apple に適したプロバイダ ID で構成された FederatedOAuthProviderData のインスタンスを作成します。

    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. Auth プロバイダ オブジェクトを使用して Firebase での認証を行います。こうすると、他の FirebaseAuth オペレーションとは異なり、ユーザーが認証情報を入力できるウェブビューをポップアップ表示して UI を制御することになります。

    ログインフローを開始するには、signInWithProvider を呼び出します。

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

    その後、アプリケーションは待機するか、Future にコールバックを登録します。

  6. 同じパターンを ReauthenticateWithProvider でも使用できます。これは、ログインしてから短時間のうちに行うべき機密性の高い操作のために、最新の認証情報を取得するのに使われます。

    firebase::Future<firebase::auth::SignInResult> result =
      user->ReauthenticateWithProvider(provider_data);
    

    その後、アプリケーションは待機するか、Future にコールバックを登録します。

  7. また、linkWithCredential() を使用して、複数の ID プロバイダを既存のアカウントにリンクすることができます。

    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::SignInResult> result =
        auth.getCurrentUser().linkWithCredential(credential);
    

Apple Notes でログイン

Firebase Auth でサポートされている他のプロバイダとは異なり、Apple では写真の URL が提供されません。

また、ユーザーがアプリとメールの共有を行わない場合、Apple はそのユーザーに固有のメールアドレス(xyz@privaterelay.appleid.com の形式)をプロビジョニングし、これがアプリと共有されます。プライベート メールリレー サービスを構成した場合、Apple は、匿名化されたアドレスに送信されたメールを、ユーザーの実際のメールアドレスに転送します。

Apple が表示名などのユーザー情報をアプリと共有するのは、ユーザーの初回ログイン時のみです。通常、ユーザーが初めて Apple でログインしたときに Firebase で表示名が保存されます。この情報は getCurrentUser().getDisplayName() で取得できます。ただし、以前に Apple でアプリへのユーザーのログインを行った際に Firebase を使用していなかった場合、Apple はユーザーの表示名を Firebase に提供しません。

次のステップ

ユーザーが初めてログインすると、新しいユーザー アカウントが作成され、ユーザーがログインに使用した認証情報(ユーザー名とパスワード、電話番号、または認証プロバイダ情報)にアカウントがリンクされます。この新しいアカウントは Firebase プロジェクトの一部として保存され、ユーザーのログイン方法にかかわらず、プロジェクトのすべてのアプリでユーザーを識別するために使用できます。

アプリでは、firebase::auth::user オブジェクトからユーザーの基本的なプロフィール情報を取得できます。ユーザーの管理についての記事をご覧ください。

Firebase Realtime Database と Cloud Storage のセキュリティ ルールでは、ログイン済みユーザーの一意の ユーザー ID を auth 変数から取得し、それを使用して、ユーザーがアクセス可能なデータを制御できます。