保存した認証情報でユーザーをログインさせる

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

ワンタップのログイン UI

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

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

One Tap ログインを使用する場所

アプリでユーザーのログインが必要な場合は、ログイン画面に One Tap UI を表示します。これは、「Google でログイン」ボタンがすでにある場合にも役立ちます。One Tap UI は、それまでユーザーがログインに使用した認証情報のみを表示するように構成できるため、以前ログインしたことのない頻度の低いユーザーに対して、アプリでの新規アカウント作成を防止できます。

アプリでログインが必須でない場合は、ログイン機能が強化された任意の画面でワンタップ ログインを使用することを検討してください。たとえば、ログインしていないときでもアプリでコンテンツをブラウジングできるが、ログイン後にのみコメントの投稿やショッピング カートへのアイテムの追加が可能であれば、ワンタップでログインするのに適した環境です。

ログイン オプション アプリでも、上記の理由により、ログイン画面で One Tap ログイン画面を使用する必要があります。

始める前に

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

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

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

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

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

  • 承認済みアカウントでフィルタリングするようにクライアントを構成します。このオプションを有効にすると、One Tap クライアントは、ユーザーが以前に使用した 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. ログインしているユーザーを確認する

ログインしているユーザーまたはログアウトしたユーザーがアクティビティを使用できる場合は、ワンタップのログイン UI を表示する前に、ユーザーのステータスを確認してください。

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

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

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

ユーザーがアプリの保存済みの認証情報を持っている場合、One Tap クライアントは成功リスナーを呼び出します。成功リスナーで、Task の結果から保留中のインテントを取得し、startIntentSenderForResult() に渡して、One Tap ログイン UI を起動します。

ユーザーが保存済みの認証情報を持っていない場合、One Tap クライアントは失敗リスナーを呼び出します。この場合は、特別なアクションは必要なく、単にアプリのログアウト状態の画面を表示するだけで済みます。ただし、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. ユーザーの応答を処理する

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

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

ユーザーがアプリと認証情報を共有することを選択した場合は、onActivityResult() のインテント データを One Tap クライアントの 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 がスローされます。このような場合は、ワンタップのログイン UI を一時的に無効にして、プロンプトを繰り返し表示して、ユーザーに不快感を与えないようにする必要があります。次の例では、アクティビティにプロパティを設定しています。このプロパティを使用して、ユーザーにワンタップ ログインを提供するかどうかを決定します。ただし、値を 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. ログアウトを処理する

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

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

次のステップ

Google 認証情報を取得するように One Tap クライアントを構成した場合、アプリはユーザーの Google アカウントを表す Google ID トークンを取得できるようになりました。これらのトークンをバックエンドで使用する方法をご覧ください。

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