リンクされたアカウントでのログインを使用すると、すでに Google アカウントをサービスにリンクしたユーザーは、Google でワンタップでログインできます。これにより、ユーザー名とパスワードを再入力することなくワンクリックでログインできるため、ユーザー エクスペリエンスが向上します。また、ユーザーがサービスで重複するアカウントを作成する可能性も低くなります。
リンクされたアカウントのログインは、Android のワンタップ ログインフローの一部として利用できます。つまり、アプリでワンタップ機能がすでに有効になっている場合は、別のライブラリをインポートする必要はありません。
このドキュメントでは、リンクされたアカウントへのログインをサポートするように Android アプリを変更する方法について説明します。
仕組み
- ワンタップ ログインのフロー中にリンクされたアカウントの表示にオプトインします。
- ユーザーが Google にログインしていて、Google アカウントをサービスのアカウントにリンク済みの場合は、リンクされたアカウントの ID トークンが返されます。
- ユーザーには、リンクされたアカウントでサービスにログインするオプションとともに、ワンタップ ログイン メッセージが表示されます。
- ユーザーがリンクされたアカウントで続行することを選択すると、ユーザーの ID トークンがアプリに返されます。これをステップ 2 でサーバーに送信されたトークンと照合して、ログインしているユーザーを識別します。
セットアップ
開発環境を設定する
開発用ホストで最新の Google Play 開発者サービスを入手します。
- Android SDK Manager を開きます。
[SDK Tools] で [Google Play 開発者サービス] を探します。
これらのパッケージのステータスが [Installed] でない場合は、両方を選択して [Install Packages] をクリックします。
アプリを設定する
プロジェクト レベルの
build.gradle
ファイルで、buildscript
セクションとallprojects
セクションの両方に Google の Maven リポジトリを含めます。buildscript { repositories { google() } } allprojects { repositories { google() } }
「Link with Google」API の依存関係をモジュールのアプリレベルの Gradle ファイル(通常は
app/build.gradle
)に追加します。dependencies { implementation 'com.google.android.gms:play-services-auth:21.2.0' }
リンクされたアカウントへのログインをサポートするように Android アプリを変更する
リンクされたアカウントへのログインフローが終了すると、ID トークンがアプリに返されます。ユーザーがログインする前に、ID トークンの整合性を検証する必要があります。
次のコードサンプルでは、ID トークンの取得、ID トークンの検証、ユーザーのログインを行う手順を詳しく説明しています。
ログイン インテントの結果を受け取るアクティビティを作成する
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") } });
ログイン リクエストを作成する
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()
ログイン保留中インテントを起動する
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 でホストされているドメインに属していません。