保存済みの認証情報でユーザーのログインを行う

One Tap サインイン クライアントを使用して、以前にアプリへのログインに使用した認証情報のいずれかを取得する許可をユーザーにリクエストします。これらの認証情報は、Google アカウントか、Chrome、Android の自動入力、または Smart Lock for Passwords を使用して Google に保存したユーザー名とパスワードの組み合わせのいずれかです。

ワンタップ ログイン UI

認証情報が正常に取得されると、それを使用してユーザーをアプリにスムーズにログインさせることができます。

ユーザーが認証情報を保存していない場合、UI は表示されず、通常のログアウト状態のエクスペリエンスを提供できます。

One Tap サインインはどこで使用すればよいですか?

アプリでユーザーのログインが必要な場合は、ログイン画面にワンタップ UI を表示します。これは、すでに [Google でログイン] ボタンがある場合でも役立ちます。ワンタップ UI は、ユーザーが以前にログインに使用した認証情報のみを表示するように構成できるため、ログイン頻度の低いユーザーに、前回ログインした方法を思い出させ、アプリで誤って新しいアカウントを作成することを防ぐことができます。

アプリでログインが任意である場合は、ログインによってエクスペリエンスが向上する画面で One Tap サインインを使用することを検討してください。たとえば、ユーザーがアプリでコンテンツを閲覧する際はログイン不要で、コメントを投稿したり、ショッピング カートに商品を追加したりする際にログインが必要になる場合は、One Tap サインインが適しています。

ログインが必須でないアプリでも、同じ理由から、ログイン画面で One Tap サインインを使用する必要があります。

始める前に

  • One Tap サインインを使ってみるの説明に沿って、Google Cloud コンソール プロジェクトと Android プロジェクトを設定します。
  • パスワード ベースのログインをサポートしている場合は、自動入力用にアプリを最適化(または Smart Lock for Passwords を使用)して、ユーザーがログイン後にパスワード認証情報を保存できるようにします。

1. One Tap サインイン クライアントを構成する

One Tap サインイン クライアントは、保存済みのパスワード、保存済みの Google アカウント、またはその両方を使用してユーザーをログインさせるように構成できます。(新規ユーザーがワンタップでアカウントを作成できるようにし、できるだけ多くのリピーターが自動またはワンタップでログインできるように、両方をサポートすることをおすすめします)。

アプリでパスワードベースのログインを使用している場合は、setPasswordRequestOptions() を使用してパスワード認証情報のリクエストを有効にします。

アプリで Google ログインを使用している場合は、setGoogleIdTokenRequestOptions() を使用して Google ID トークン リクエストを有効にして構成します。

  • サーバー クライアント ID を Google Cloud コンソールで作成した ID に設定します。これはサーバーのクライアント ID であり、Android クライアント ID ではありません。

  • 承認済みアカウントでフィルタするようにクライアントを構成します。このオプションを有効にすると、ワンタップ クライアントは、過去に使用した Google アカウントでアプリにログインするようユーザーに求めるだけになります。これにより、ユーザーがアカウントをすでに持っているかどうか、またはどの Google アカウントを使用したかわからない場合でも、ユーザーは正常にログインできます。また、ユーザーがアプリで誤って新しいアカウントを作成することを防ぐことができます。

  • 可能であれば、ユーザーを自動的にログインさせる場合は、setAutoSelectEnabled() を使用してこの機能を有効にします。次の条件が満たされている場合、自動ログインが可能です。

    • ユーザーはアプリの認証情報を 1 つだけ保存している。つまり、保存したパスワードまたは Google アカウントが 1 つだけある。
    • ユーザーが Google アカウント設定で自動ログインを無効にしていない。
  • これは省略可能ですが、ログインのセキュリティを強化し、リプレイ攻撃を回避するために、ノンスの使用を強くおすすめします。setNonce を使用して、各リクエストにノンスを含めます。ノンスの生成に関する提案と詳細については、SafetyNet のノンスを取得するセクションをご覧ください。

Java

public class YourActivity extends AppCompatActivity {
    // ...

    private SignInClient oneTapClient;
    private BeginSignInRequest signInRequest;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState,
                         @Nullable PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);

        oneTapClient = Identity.getSignInClient(this);
        signInRequest = BeginSignInRequest.builder()
                .setPasswordRequestOptions(PasswordRequestOptions.builder()
                        .setSupported(true)
                        .build())
                .setGoogleIdTokenRequestOptions(GoogleIdTokenRequestOptions.builder()
                        .setSupported(true)
                        // Your server's client ID, not your Android client ID.
                        .setServerClientId(getString(R.string.default_web_client_id))
                        // Only show accounts previously used to sign in.
                        .setFilterByAuthorizedAccounts(true)
                        .build())
                // Automatically sign in when exactly one credential is retrieved.
                .setAutoSelectEnabled(true)
                .build();
        // ...
    }
    // ...
}

Kotlin

class YourActivity : AppCompatActivity() {
      // ...

      private lateinit var oneTapClient: SignInClient
      private lateinit var signInRequest: BeginSignInRequest

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)

          oneTapClient = Identity.getSignInClient(this)
          signInRequest = BeginSignInRequest.builder()
              .setPasswordRequestOptions(BeginSignInRequest.PasswordRequestOptions.builder()
                  .setSupported(true)
                  .build())
              .setGoogleIdTokenRequestOptions(
                  BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
                      .setSupported(true)
                      // Your server's client ID, not your Android client ID.
                      .setServerClientId(getString(R.string.your_web_client_id))
                      // Only show accounts previously used to sign in.
                      .setFilterByAuthorizedAccounts(true)
                      .build())
              // Automatically sign in when exactly one credential is retrieved.
              .setAutoSelectEnabled(true)
              .build()
          // ...
      }
      // ...
  }

2. ログインしているユーザーを確認する

ログインしているユーザーまたはログインしていないユーザーが Activity を使用できる場合は、One Tap サインイン UI を表示する前にユーザーのステータスを確認します。

また、ユーザーがプロンプトを閉じるか、プロンプトの外側をタップして、One Tap サインインの使用をすでに拒否しているかどうかも追跡する必要があります。これは、アクティビティのブール値プロパティにすることができます。(下記のワンタップ UI の表示を停止するをご覧ください)。

3. One Tap サインイン UI を表示する

ユーザーがログインしておらず、ワンタップ サインインの使用を拒否していない場合は、クライアント オブジェクトの beginSignIn() メソッドを呼び出し、返される Task にリスナーをアタッチします。通常、アプリは、アクティビティの onCreate() メソッドで、または単一アクティビティ アーキテクチャを使用している場合は画面遷移後に、この処理を行います。

  • 保存済みの認証情報が見つかった場合: ユーザーがアプリの保存済みの認証情報を持っている場合、One Tap クライアントは成功リスナーを呼び出します。成功リスナーで、Task の結果からペンディング インテントを取得し、startIntentSenderForResult() に渡して One Tap サインイン UI を起動します。
  • 保存された認証情報が見つからない: ワンタップ クライアントは失敗リスナーを呼び出します。この場合、対応は必要ありません。アプリのログアウト時のエクスペリエンスを引き続き表示できます。ただし、One Tap サインアップをサポートしている場合は、ここでそのフローを開始して、シームレスなアカウント作成エクスペリエンスを実現できます。ワンタップで新しいアカウントを作成するをご覧ください。

Java

oneTapClient.beginSignIn(signUpRequest)
        .addOnSuccessListener(this, new OnSuccessListener<BeginSignInResult>() {
            @Override
            public void onSuccess(BeginSignInResult result) {
                try {
                    startIntentSenderForResult(
                            result.getPendingIntent().getIntentSender(), REQ_ONE_TAP,
                            null, 0, 0, 0);
                } catch (IntentSender.SendIntentException e) {
                    Log.e(TAG, "Couldn't start One Tap UI: " + e.getLocalizedMessage());
                }
            }
        })
        .addOnFailureListener(this, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // No saved credentials found. Launch the One Tap sign-up flow, or
                // do nothing and continue presenting the signed-out UI.
                Log.d(TAG, e.getLocalizedMessage());
            }
        });

Kotlin

oneTapClient.beginSignIn(signInRequest)
    .addOnSuccessListener(this) { result ->
        try {
            startIntentSenderForResult(
                result.pendingIntent.intentSender, REQ_ONE_TAP,
                null, 0, 0, 0, null)
        } catch (e: IntentSender.SendIntentException) {
            Log.e(TAG, "Couldn't start One Tap UI: ${e.localizedMessage}")
        }
    }
    .addOnFailureListener(this) { e ->
        // No saved credentials found. Launch the One Tap sign-up flow, or
        // do nothing and continue presenting the signed-out UI.
        Log.d(TAG, e.localizedMessage)
    }

4. ユーザーの回答に対応する

One Tap サインイン プロンプトに対するユーザーの応答は、アクティビティの onActivityResult() メソッドを使用してアプリに報告されます。ユーザーがログインを選択した場合、結果は保存された認証情報になります。ユーザーがワンタップ UI を閉じるか、UI の外側をタップしてログインを拒否した場合、結果はコード RESULT_CANCELED で返されます。アプリは両方の可能性に対応する必要があります。

取得した認証情報でログインする

ユーザーがアプリと認証情報を共有することを選択した場合は、onActivityResult() からワンタップ クライアントの getSignInCredentialFromIntent() メソッドにインテント データを渡すことで、認証情報を取得できます。ユーザーが Google アカウントの認証情報をアプリと共有している場合、認証情報には null 以外の googleIdToken プロパティが含まれます。ユーザーが保存したパスワードを共有している場合、認証情報には null 以外の password プロパティが含まれます。

認証情報を使用してアプリのバックエンドで認証します。

  • ユーザー名とパスワードのペアが取得された場合は、ユーザーが手動で入力した場合と同じ方法でログインします。
  • Google アカウントの認証情報が取得された場合は、ID トークンを使用してバックエンドで認証します。リプレイ攻撃を回避するためにノンスを使用することを選択した場合は、バックエンド サーバーでレスポンス値を確認します。ID トークンを使用してバックエンドで認証するをご覧ください。

Java

public class YourActivity extends AppCompatActivity {

    // ...
    private static final int REQ_ONE_TAP = 2;  // Can be any integer unique to the Activity.
    private boolean showOneTapUI = true;
    // ...

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode) {
            case REQ_ONE_TAP:
                try 
                    SignInCredential credential = oneTapClient.getSignInCredentialFromIntent(data);
                    String idToken = credential.getGoogleIdToken();
                    String username = credential.getId();
                    String password = credential.getPassword();
                    if (idToken !=  null) {
                        // Got an ID token from Google. Use it to authenticate
                        // with your backend.
                        Log.d(TAG, "Got ID token.");
                    } else if (password != null) {
                        // Got a saved username and password. Use them to authenticate
                        // with your backend.
                        Log.d(TAG, "Got password.");
                    }
                } catch (ApiException e) {
                    // ...
                }
                break;
        }
    }
}

Kotlin

class YourActivity : AppCompatActivity() {

      // ...
      private val REQ_ONE_TAP = 2  // Can be any integer unique to the Activity
      private var showOneTapUI = true
      // ...

      override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
          super.onActivityResult(requestCode, resultCode, data)

          when (requestCode) {
               REQ_ONE_TAP -> {
                  try 
                      val credential = oneTapClient.getSignInCredentialFromIntent(data)
                      val idToken = credential.googleIdToken
                      val username = credential.id
                      val password = credential.password
                      when {
                          idToken != null -> {
                              // Got an ID token from Google. Use it to authenticate
                              // with your backend.
                              Log.d(TAG, "Got ID token.")
                          }
                          password != null -> {
                              // Got a saved username and password. Use them to authenticate
                              // with your backend.
                              Log.d(TAG, "Got password.")
                          }
                          else -> {
                              // Shouldn't happen.
                              Log.d(TAG, "No ID token or password!")
                          }
                      }
                  } catch (e: ApiException) {
                      // ...
                  }
              }
          }
      }
      // ...
  }

ワンタップ UI の表示を停止する

ユーザーがログインを拒否した場合、getSignInCredentialFromIntent() の呼び出しは CommonStatusCodes.CANCELED ステータス コードを含む ApiException をスローします。このような場合は、One Tap サインイン UI を一時的に無効にして、ユーザーに繰り返しプロンプトが表示されないようにする必要があります。次の例では、Activity にプロパティを設定し、そのプロパティを使用してユーザーに One Tap サインインを提供するかどうかを判断することで、これを実現しています。ただし、SharedPreferences に値を保存したり、他の方法を使用したりすることもできます。

One Tap サインインのログイン メッセージのレート制限を独自に実装することが重要です。そうしないと、ユーザーが複数のプロンプトを連続してキャンセルした場合、ワンタップ クライアントは次の 24 時間、ユーザーにプロンプトを表示しません。

Java

public class YourActivity extends AppCompatActivity {

    // ...
    private static final int REQ_ONE_TAP = 2;  // Can be any integer unique to the Activity.
    private boolean showOneTapUI = true;
    // ...

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode) {
            case REQ_ONE_TAP:
                try {
                    // ...
                } catch (ApiException e) {
                    switch (e.getStatusCode()) {
                        case CommonStatusCodes.CANCELED:
                            Log.d(TAG, "One-tap dialog was closed.");
                            // Don't re-prompt the user.
                            showOneTapUI = false;
                            break;
                        case CommonStatusCodes.NETWORK_ERROR:
                            Log.d(TAG, "One-tap encountered a network error.");
                            // Try again or just ignore.
                            break;
                        default:
                            Log.d(TAG, "Couldn't get credential from result."
                                    + e.getLocalizedMessage());
                            break;
                    }
                }
                break;
        }
    }
}

Kotlin

class YourActivity : AppCompatActivity() {

      // ...
      private val REQ_ONE_TAP = 2  // Can be any integer unique to the Activity
      private var showOneTapUI = true
      // ...

      override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
          super.onActivityResult(requestCode, resultCode, data)

          when (requestCode) {
              REQ_ONE_TAP -> {
                  try {
                      // ...
                  } catch (e: ApiException) {
                      when (e.statusCode) {
                          CommonStatusCodes.CANCELED -> {
                              Log.d(TAG, "One-tap dialog was closed.")
                              // Don't re-prompt the user.
                              showOneTapUI = false
                          }
                          CommonStatusCodes.NETWORK_ERROR -> {
                              Log.d(TAG, "One-tap encountered a network error.")
                              // Try again or just ignore.
                          }
                          else -> {
                              Log.d(TAG, "Couldn't get credential from result." +
                                  " (${e.localizedMessage})")
                          }
                      }
                  }
              }
          }
      }
      // ...
  }

5. ログアウトを処理する

ユーザーがアプリからログアウトしたら、ワンタップ クライアントの signOut() メソッドを呼び出します。signOut() を呼び出すと、ユーザーが再度ログインするまで自動ログインが無効になります。

自動ログインを使用しない場合でも、このステップは重要です。このステップにより、ユーザーがアプリからログアウトしたときに、使用している Play 開発者サービス API の認証状態もリセットされるようになります。

次のステップ

Google 認証情報を取得するようにワンタップ クライアントを構成すると、アプリはユーザーの Google アカウントを表す Google ID トークンを取得できるようになります。バックエンドでこれらのトークンを使用する方法を確認する。

Google ログインをサポートしている場合は、ワンタップ クライアントを使用して、アプリにスムーズなアカウント作成フローを追加することもできます。