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

Google でログインを使用すると、ユーザー認証を Android アプリにすばやく統合できます。ユーザーは Google アカウントを使用してアプリにログインし、同意を提供して、プロフィール情報をアプリと安全に共有できます。Android の Credential Manager Jetpack ライブラリを使用すると、この統合がスムーズになり、単一の API を使用して Android デバイス全体で一貫したエクスペリエンスを提供できます。

このドキュメントでは、Android アプリに「Google でログイン」を実装する方法、Google でログイン ボタンの UI を設定する方法、アプリに最適化されたワンタップ登録とログインのエクスペリエンスを構成する方法について説明します。デバイスの移行をスムーズに行うため、Google でログインは自動ログインをサポートしています。また、Android、iOS、ウェブの各サーフェスにわたるクロス プラットフォームの性質により、どのデバイスでもアプリのログイン アクセスを提供できます。アプリケーションで Firebase Authentication を使用している場合は、Android で Google を使用して認証するガイドで、Google でログインと Credential Manager の統合について詳しく確認できます。

Google でログインを設定する主な手順は次の 2 つです。

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

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

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

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

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

プロジェクトを設定する

  1. でプロジェクトを開くか、まだ作成していない場合はプロジェクトを作成します。
  2. で、すべての情報が完全かつ正確であることを確認します。
    1. アプリに正しいアプリ名、アプリのロゴ、アプリのホームページが割り当てられていることを確認します。これらの値は、登録時の [Google でログイン] の同意画面と [サードパーティ製のアプリとサービス] 画面でユーザーに表示されます。
    2. アプリのプライバシー ポリシーと利用規約の URL を指定していることを確認します。
  3. で、アプリの Android クライアント ID を作成します(まだ作成していない場合)。アプリのパッケージ名と SHA-1 署名を指定する必要があります。
    1. に移動します。
    2. [クライアントを作成] をクリックします。
    3. アプリケーションの種類として [Android] を選択します。
  4. で、まだ作成していない場合は、新しい「ウェブ アプリケーション」クライアント ID を作成します。[承認済みの JavaScript 生成元] と [承認済みのリダイレクト URI] のフィールドは、今のところ無視してかまいません。このクライアント ID は、バックエンド サーバーが Google の認証サービスと通信する際に、バックエンド サーバーを識別するために使用されます。
    1. に移動します。
    2. [クライアントを作成] をクリックします。
    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)
  // nonce string to use when generating a Google ID token
  .setNonce(nonce)
.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)
  // nonce string to use when generating a Google ID token
  .setNonce(nonce)
.build()

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

セキュリティを強化するために nonce を設定する

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

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(true)
  .setServerClientId(WEB_CLIENT_ID)
  .setAutoSelectEnabled(true)
  // nonce string to use when generating a Google ID token
  .setNonce(nonce)
.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 = GetCredentialRequest.Builder()
  .addCredentialOption(googleIdOption)
  .build()

coroutineScope {
  try {
    val result = credentialManager.getCredential(
      request = request,
      context = activityContext,
    )
    handleSignIn(result)
  } catch (e: GetCredentialException) {
    // Handle failure
  }
}
fun handleSignIn(result: GetCredentialResponse) {
  // Handle the successfully returned credential.
  val credential = result.credential
  val responseJson: String

  when (credential) {

    // Passkey credential
    is PublicKeyCredential -> {
      // Share responseJson such as a GetCredentialResponse to 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.
          // see [validation instructions](https://developers.google.com/identity/gsi/web/guides/verify-google-id-token)
        } 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(
  serverClientId = WEB_CLIENT_ID
).setNonce(nonce)
  .build()

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

fun handleSignInWithGoogleOption(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 を呼び出して、保存されている認証情報セッションをプロバイダに消去させる必要があります。