Android 用リンクされたアカウントへのログイン

リンクされたアカウント ログインを使用すると、すでに Google アカウントをサービスにリンクしているユーザーは、ワンタップで Google でログインできます。これにより、ユーザー名とパスワードを再入力しなくてもワンクリックでログインできるようになるため、ユーザー エクスペリエンスが向上します。また、ユーザーがサービスに対して重複するアカウントを作成する可能性も減ります。

リンクされたアカウントのログインは、Android のワンタップ ログインフローの一部として利用できます。つまり、アプリでワンタップ機能がすでに有効になっている場合は、別のライブラリをインポートする必要はありません。

このドキュメントでは、リンクされたアカウントのログインをサポートするように Android アプリを変更する方法について説明します。

仕組み

  1. ワンタップ ログインフロー中に、リンクされたアカウントを表示することを選択できます。
  2. ユーザーが Google にログインし、Google アカウントをサービスのアカウントにリンクしている場合は、リンクされたアカウントの ID トークンが返されます。
  3. リンクされたアカウントでサービスにログインするオプションを含むワンタップのログイン プロンプトがユーザーに表示されます。
  4. ユーザーがリンクされたアカウントで続行を選択すると、ユーザーの ID トークンがアプリに返されます。これをステップ 2 でサーバーに送信されたトークンと照合して、ログインしているユーザーを特定します。

設定

開発環境をセットアップする

開発用ホストで最新の Google Play 開発者サービスを取得します。

  1. Android SDK Manager を開きます。
  1. [SDK Tools] で [Google Play 開発者サービス] を探します。

  2. これらのパッケージのステータスが [Installed] でない場合は、両方を選択して [Install Packages] をクリックします。

アプリを設定する

  1. プロジェクト レベルの build.gradle ファイルの buildscript セクションと allprojects セクションの両方に Google の Maven リポジトリを含めます。

    buildscript {
        repositories {
            google()
        }
    }
    
    allprojects {
        repositories {
            google()
        }
    }
    
  2. 「Link with Google」API の依存関係をモジュールのアプリレベルの Gradle ファイル(通常は app/build.gradle)に追加します。

    dependencies {
      implementation 'com.google.android.gms:play-services-auth:21.0.0'
    }
    

リンクされたアカウントのログインをサポートするように Android アプリを変更する

リンクされたアカウントのログインフローの終了時に、ID トークンがアプリに返されます。ID トークンの整合性は、ユーザーのログイン前に検証する必要があります。

次のコードサンプルは、ID トークンを取得して確認し、その後でユーザーのログインを行う手順を示しています。

  1. ログイン インテントの結果を受け取るアクティビティを作成する

    Kotlin

      private val activityResultLauncher = registerForActivityResult(
        ActivityResultContracts.StartIntentSenderForResult()) { result ->
        if (result.resultCode == RESULT_OK) {
          try {
            val signInCredentials = Identity.signInClient(this)
                                    .signInCredentialFromIntent(result.data)
            // Review the Verify the integrity of the ID token section for
            // details on how to verify the ID token
            verifyIdToken(signInCredential.googleIdToken)
          } catch (e: ApiException) {
            Log.e(TAG, "Sign-in failed with error code:", e)
          }
        } else {
          Log.e(TAG, "Sign-in failed")
        }
      }
    

    Java

      private final ActivityResultLauncher<IntentSenderResult>
        activityResultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartIntentSenderForResult(),
        result -> {
        If (result.getResultCode() == RESULT_OK) {
            try {
              SignInCredential signInCredential = Identity.getSignInClient(this)
                             .getSignInCredentialFromIntent(result.getData());
              verifyIdToken(signInCredential.getGoogleIdToken());
            } catch (e: ApiException ) {
              Log.e(TAG, "Sign-in failed with error:", e)
            }
        } else {
            Log.e(TAG, "Sign-in failed")
        }
    });
    
  2. ログイン リクエストを作成する

    Kotlin

    private val tokenRequestOptions =
    GoogleIdTokenRequestOptions.Builder()
      .supported(true)
      // Your server's client ID, not your Android client ID.
      .serverClientId(getString("your-server-client-id")
      .filterByAuthorizedAccounts(true)
      .associateLinkedAccounts("service-id-of-and-defined-by-developer",
                               scopes)
      .build()
    

    Java

     private final GoogleIdTokenRequestOptions tokenRequestOptions =
         GoogleIdTokenRequestOptions.Builder()
      .setSupported(true)
      .setServerClientId("your-service-client-id")
      .setFilterByAuthorizedAccounts(true)
      .associateLinkedAccounts("service-id-of-and-defined-by-developer",
                                scopes)
      .build()
    
  3. ログイン保留中インテントを起動する

    Kotlin

     Identity.signInClient(this)
        .beginSignIn(
      BeginSignInRequest.Builder()
        .googleIdTokenRequestOptions(tokenRequestOptions)
      .build())
        .addOnSuccessListener{result ->
          activityResultLauncher.launch(result.pendingIntent.intentSender)
      }
      .addOnFailureListener {e ->
        Log.e(TAG, "Sign-in failed because:", e)
      }
    

    Java

     Identity.getSignInClient(this)
      .beginSignIn(
        BeginSignInRequest.Builder()
          .setGoogleIdTokenRequestOptions(tokenRequestOptions)
          .build())
      .addOnSuccessListener(result -> {
        activityResultLauncher.launch(
            result.getPendingIntent().getIntentSender());
    })
    .addOnFailureListener(e -> {
      Log.e(TAG, "Sign-in failed because:", e);
    });
    

ID トークンの整合性を検証する

トークンが有効であることを確認するには、次の条件を満たしていることを確認します。

  • ID トークンが Google によって適切に署名されている。Google の公開鍵(JWK または PEM 形式で入手可能)を使用して、トークンの署名を検証します。これらの鍵は定期的にローテーションされます。レスポンスの Cache-Control ヘッダーを調べて、鍵を再度取得する必要があるタイミングを判断します。
  • ID トークン内の aud の値が、アプリのクライアント ID のいずれかと等しい。このチェックは、悪意のあるアプリに対して発行された ID トークンが、アプリのバックエンド サーバー上の同じユーザーのデータにアクセスするために使用されることを防ぐために必要です。
  • ID トークン内の iss の値が accounts.google.com または https://accounts.google.com であること。
  • ID トークンの有効期限(exp)が過ぎていません。
  • ID トークンが Google Workspace または Cloud の組織アカウントを表すことを検証する必要がある場合は、ユーザーのホスト ドメインを示す hd クレームを確認できます。リソースへのアクセスを特定のドメインのメンバーのみに制限する場合は、このオプションを使用する必要があります。このクレームがない場合、アカウントは Google がホストするドメインに属していません。

これらの検証手順を実行するコードを独自に記述するのではなく、お使いのプラットフォーム用の Google API クライアント ライブラリまたは汎用の JWT ライブラリを使用することを強くおすすめします。開発とデバッグの場合は、tokeninfo 検証エンドポイントを呼び出すことができます。

Google API クライアント ライブラリを使用する

本番環境で Google ID トークンを検証するには、Java Google API クライアント ライブラリの使用をおすすめします。

Java

  import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
  import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
  import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;

  ...

  GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
      // Specify the CLIENT_ID of the app that accesses the backend:
      .setAudience(Collections.singletonList(CLIENT_ID))
      // Or, if multiple clients access the backend:
      //.setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3))
      .build();

  // (Receive idTokenString by HTTPS POST)

  GoogleIdToken idToken = verifier.verify(idTokenString);
  if (idToken != null) {
    Payload payload = idToken.getPayload();

    // Print user identifier
    String userId = payload.getSubject();
    System.out.println("User ID: " + userId);

    // Get profile information from payload
    String email = payload.getEmail();
    boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
    String name = (String) payload.get("name");
    String pictureUrl = (String) payload.get("picture");
    String locale = (String) payload.get("locale");
    String familyName = (String) payload.get("family_name");
    String givenName = (String) payload.get("given_name");

    // Use or store profile information
    // ...

  } else {
    System.out.println("Invalid ID token.");
  }

GoogleIdTokenVerifier.verify() メソッドは、JWT 署名、aud クレーム、iss クレーム、exp クレームを検証します。

ID トークンが Google Workspace または Cloud の組織アカウントを表すことを検証する必要がある場合は、Payload.getHostedDomain() メソッドから返されたドメイン名を確認して hd クレームを検証できます。

tokeninfo エンドポイントの呼び出し

tokeninfo エンドポイントを使用すると、デバッグのために ID トークンの署名を簡単に検証できます。このエンドポイントを呼び出すと、追加のネットワーク リクエストが必要になりますが、検証の大部分は、独自のコードで適切な検証とペイロード抽出のテストを行います。リクエストがスロットリングされたり、断続的にエラーが発生する可能性があるため、本番環境コードでの使用には適していません。

tokeninfo エンドポイントを使用して ID トークンを検証するには、エンドポイントに HTTPS POST または GET リクエストを作成し、id_token パラメータで ID トークンを渡します。たとえば、トークン「XYZ123」を検証するには、次の GET リクエストを作成します。

https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123

トークンが適切に署名され、iss クレームと exp クレームに想定される値がある場合は、HTTP 200 レスポンスが返されます。本文には JSON 形式の ID トークン クレームが含まれます。以下はレスポンスの例です。

{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

ID トークンが Google Workspace アカウントを表すことを検証する必要がある場合は、ユーザーのホスト ドメインを示す hd クレームを確認できます。リソースへのアクセスを特定のドメインのメンバーのみに制限する場合は、このオプションを使用する必要があります。このクレームがない場合、アカウントは Google Workspace でホストされているドメインに属していません。