「Google でログイン」でユーザーを認証する

Google でログインを使用すると、ユーザー認証を Android アプリに簡単に統合できます。ユーザーは Google アカウントを使用してアプリにログインし、同意を表明し、プロファイル情報をアプリと安全に共有できます。Android の認証情報マネージャー Jetpack ライブラリを使用すると、この統合をスムーズに行うことができ、単一の API を使用して Android デバイス間で一貫したエクスペリエンスを提供できます。

このドキュメントでは、Android アプリに「Google でログイン」を実装する方法、「Google でログイン」ボタンの UI を設定する方法、アプリ用に最適化されたワンタップによる登録とログイン エクスペリエンスを設定する方法について説明します。デバイスの移行をスムーズに行うため、「Google でログイン」は自動ログインをサポートしています。また、Android、iOS、ウェブ サーフェス全体でクロスプラットフォームであるため、どのデバイスでもアプリのログイン アクセスを提供できます。

[Google でログイン] を設定するには、次の 2 つの主な手順を行います。

認証情報マネージャーのボトムシート UI のオプションとして「Google でログイン」を構成する。ログインを求めるメッセージが自動的に表示されるように設定できます。パスキーまたはパスワードを実装している場合は、関連するすべての認証情報の種類を同時にリクエストできます。これにより、ユーザーは以前にログインに使用したオプションを覚えておく必要がなくなります。

認証情報マネージャーのボトムシート
図 1. 認証情報マネージャーのボトムシート認証情報選択 UI

[Google でログイン] ボタンをアプリの UI に追加します。[Google でログイン] ボタンを使用すると、ユーザーは既存の Google アカウントを使用して Android アプリに登録またはログインできます。ユーザーがボトムシート UI を閉じる場合や、登録とログインに Google アカウントを明示的に使用する場合、[Google でログイン] ボタンをクリックします。デベロッパーにとって、これはユーザーのオンボーディングが容易になり、登録時の手間を軽減できることを意味します。

「Google でログイン」のフローを示すアニメーション
図 2. 認証情報マネージャーの [Google でログイン] ボタンの UI

このドキュメントでは、Google ID ヘルパー ライブラリを使用して、「Google でログイン」ボタンとボトムシート ダイアログを認証情報マネージャー API と統合する方法について説明します。

Google API コンソール プロジェクトを設定する

  1. API Console でプロジェクトを開きます。プロジェクトをまだ作成していない場合は、作成します。
  2. [OAuth 同意画面] ページで、すべての情報が完全かつ正確であることを確認します。
    1. アプリに正しいアプリ名、アプリロゴ、アプリのホームページが割り当てられていることを確認します。これらの値は、登録時の [Google でログイン] の同意画面と [サードパーティ製のアプリとサービス] 画面でユーザーに表示されます。
    2. アプリのプライバシー ポリシーと利用規約の URL が指定されていることを確認します。
  3. [認証情報] ページで、アプリの Android クライアント ID を作成します(まだ作成していない場合)。アプリのパッケージ名と SHA-1 署名を指定する必要があります。
    1. [認証情報] ページに移動します。
    2. [認証情報を作成] > [OAuth クライアント ID] をクリックします。
    3. アプリケーションの種類として [Android] を選択します。
  4. [認証情報] ページで、まだ作成していない場合は新しい「ウェブ アプリケーション」クライアント ID を作成します。現時点では、[Authorized JavaScript Origins] フィールドと [Authorized redirect URIs] フィールドは無視できます。このクライアント ID は、Google の認証サービスと通信するときにバックエンド サーバーを識別するために使用されます。
    1. [認証情報] ページに移動します。
    2. [認証情報を作成] > [OAuth クライアント ID] をクリックします。
    3. ウェブ アプリケーションのタイプを選択します。

依存関係の宣言

モジュールの build.gradle ファイルで、認証情報マネージャーの最新バージョンを使用して依存関係を宣言します。

dependencies {
  // ... other dependencies

  implementation "androidx.credentials:credentials:<latest version>"
  implementation "androidx.credentials:credentials-play-services-auth:<latest version>"
  implementation "com.google.android.libraries.identity.googleid:googleid:<latest version>"
}

Google ログイン リクエストをインスタンス化する

実装を開始するには、Google ログイン リクエストをインスタンス化します。GetGoogleIdOption を使用して、ユーザーの Google ID トークンを取得します。

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(true)
  .setServerClientId(WEB_CLIENT_ID)
  .setAutoSelectEnabled(true)
  .setNonce(<nonce string to use when generating a Google ID token>)
  .build()

まず、setFilterByAuthorizedAccounts パラメータを true に設定して API を呼び出し、ユーザーがアプリへのログインに使用したことがあるアカウントがあるかどうかを確認します。ユーザーは、利用可能なアカウントから選択してログインできます。

使用できる承認済みの Google アカウントがない場合は、使用可能なアカウントで登録するようユーザーに求めるメッセージが表示されます。これを行うには、API を再度呼び出して setFilterByAuthorizedAccountsfalse に設定し、ユーザーにプロンプトを表示します。詳しくは、登録についての記事をご覧ください。

リピーター向けの自動ログインを有効にする(推奨)

デベロッパーは、単一のアカウントで登録するユーザーに対して自動ログインを有効にする必要があります。これにより、デバイス間でシームレスなエクスペリエンスが実現します。特にデバイスの移行中は、ユーザーは認証情報を再入力しなくても、アカウントに迅速にアクセスできます。これにより、ユーザーは以前にログインしたときに不要な手間を省くことができます。

自動ログインを有効にするには、setAutoSelectEnabled(true) を使用します。自動ログインは、次の条件を満たす場合にのみ可能です。

  • リクエストに一致する認証情報が 1 つあります。これは Google アカウントまたはパスワードで、この認証情報は Android デバイスのデフォルト アカウントと一致します。
  • ユーザーが明示的にログアウトしていない。
  • ユーザーが Google アカウント設定で自動ログインを無効にしていない。
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(true)
  .setServerClientId(WEB_CLIENT_ID)
  .setAutoSelectEnabled(true)
  .setNonce(<nonce string to use when generating a Google ID token>)
  .build()

自動ログインを実装する際は、ログアウトを正しく処理して、ユーザーがアプリから明示的にログアウトした後に適切なアカウントを常に選択できるようにしてください。

ノンスを設定してセキュリティを強化する

ログイン セキュリティを向上させ、リプレイ攻撃を回避するには、setNonce を追加して各リクエストにノンスを含めます。詳しくは、ノンスの生成についてをご覧ください。

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(true)
  .setServerClientId(WEB_CLIENT_ID)
  .setAutoSelectEnabled(true)
  .setNonce(<nonce string to use when generating a Google ID token>)
  .build()

「Google でログイン」フローを作成する

「Google でログイン」のフローを設定する手順は次のとおりです。

  1. GetCredentialRequest をインスタンス化し、addCredentialOption() を使用して前に作成した googleIdOption を追加して認証情報を取得します。
  2. このリクエストを getCredential()(Kotlin)または getCredentialAsync()(Java)の呼び出しに渡して、ユーザーの利用可能な認証情報を取得します。
  3. API が正常に実行されたら、GoogleIdTokenCredential データの結果を保持する CustomCredential を抽出します。
  4. CustomCredential の型は GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL の値と同じである必要があります。GoogleIdTokenCredential.createFrom メソッドを使用して、オブジェクトを GoogleIdTokenCredential に変換します。
  5. 変換が成功したら、GoogleIdTokenCredential ID を抽出して検証し、サーバー上の認証情報を認証します。

  6. GoogleIdTokenParsingException での変換ができない場合は、「Google でログイン」ライブラリのバージョンの更新が必要になることがあります。

  7. 認識されないカスタム認証情報タイプを捕捉します。

val request: GetCredentialRequest = Builder()
  .addCredentialOption(googleIdOption)
  .build()

coroutineScope.launch {
  try {
    val result = credentialManager.getCredential(
      request = request,
      context = activityContext,
    )
    handleSignIn(result)
  } catch (e: GetCredentialException) {
    handleFailure(e)
  }
}

fun handleSignIn(result: GetCredentialResponse) {
  // Handle the successfully returned credential.
  val credential = result.credential

  when (credential) {

    // Passkey credential
    is PublicKeyCredential -> {
      // Share responseJson such as a GetCredentialResponse on your server to
      // validate and authenticate
      responseJson = credential.authenticationResponseJson
    }

    // Password credential
    is PasswordCredential -> {
      // Send ID and password to your server to validate and authenticate.
      val username = credential.id
      val password = credential.password
    }

    // GoogleIdToken credential
    is CustomCredential -> {
      if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
        try {
          // Use googleIdTokenCredential and extract the ID to validate and
          // authenticate on your server.
          val googleIdTokenCredential = GoogleIdTokenCredential
            .createFrom(credential.data)
          // You can use the members of googleIdTokenCredential directly for UX
          // purposes, but don't use them to store or control access to user
          // data. For that you first need to validate the token:
          // pass googleIdTokenCredential.getIdToken() to the backend server.
          GoogleIdTokenVerifier verifier = ... // see validation instructions
          GoogleIdToken idToken = verifier.verify(idTokenString);
          // To get a stable account identifier (e.g. for storing user data),
          // use the subject ID:
          idToken.getPayload().getSubject()
        } catch (e: GoogleIdTokenParsingException) {
          Log.e(TAG, "Received an invalid google id token response", e)
        }
      } else {
        // Catch any unrecognized custom credential type here.
        Log.e(TAG, "Unexpected type of credential")
      }
    }

    else -> {
      // Catch any unrecognized credential type here.
      Log.e(TAG, "Unexpected type of credential")
    }
  }
}

[Google でログイン] ボタンのフローをトリガーする

[Google でログイン] ボタンのフローをトリガーするには、GetGoogleIdOption の代わりに GetSignInWithGoogleOption を使用します。

val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder()
  .setServerClientId(WEB_CLIENT_ID)
  .setNonce(<nonce string to use when generating a Google ID token>)
  .build()

次のコード例に示すように、返された GoogleIdTokenCredential を処理します。

fun handleSignIn(result: GetCredentialResponse) {
  // Handle the successfully returned credential.
  val credential = result.credential

  when (credential) {
    is CustomCredential -> {
      if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
        try {
          // Use googleIdTokenCredential and extract id to validate and
          // authenticate on your server.
          val googleIdTokenCredential = GoogleIdTokenCredential
            .createFrom(credential.data)
        } catch (e: GoogleIdTokenParsingException) {
          Log.e(TAG, "Received an invalid google id token response", e)
        }
      }
      else -> {
        // Catch any unrecognized credential type here.
        Log.e(TAG, "Unexpected type of credential")
      }
    }

    else -> {
      // Catch any unrecognized credential type here.
      Log.e(TAG, "Unexpected type of credential")
    }
  }
}

Google のログイン リクエストをインスタンス化したら、Google でログインのセクションで説明したのと同じ方法で認証フローを開始します。

新規ユーザーの登録を有効にする(推奨)

「Google でログイン」は、ユーザーがアプリやサービスで新しいアカウントを数回タップするだけで作成できる最も簡単な方法です。

保存された認証情報が見つからない場合(getGoogleIdOption から Google アカウントが返されない場合)、ユーザーに登録を求めます。まず、setFilterByAuthorizedAccounts(true) が存在し、以前に使用したアカウントが存在するかどうかを確認します。見つからない場合は、setFilterByAuthorizedAccounts(false) を使用して Google アカウントで登録するようユーザーに促します。

例:

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(false)
  .setServerClientId(WEB_CLIENT_ID)
  .build()

Google の登録リクエストをインスタンス化したら、認証フローを開始します。ユーザーが登録に「Google でログイン」を使用しない場合は、自動入力用にアプリを最適化することを検討してください。ユーザーがアカウントを作成したら、アカウント作成の最後のステップとしてパスキーに登録することを検討してください。

ログアウトを処理する

ユーザーがアプリからログアウトしたら、API の clearCredentialState() メソッドを呼び出して、すべての認証情報プロバイダから現在のユーザー認証情報の状態を消去します。これにより、指定したアプリの保存されている認証情報セッションを消去する必要があることをすべての認証情報プロバイダに通知します。

認証情報プロバイダは、アクティブな認証情報セッションを保存し、それを使用して、今後の get-credential 呼び出しのログイン オプションを制限する場合があります。たとえば、使用可能な他の認証情報よりも、アクティブな認証情報に優先順位を付けることができます。ユーザーがアプリから明示的にログアウトした場合、次回包括的なログイン オプションを取得するには、この API を呼び出して、保存されている認証情報セッションをプロバイダが消去できるようにする必要があります。