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

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

ワンタップ ログイン UI

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

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

One Tap サインインはどのような場合に使用すればよいですか?

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

アプリでログインが任意の場合は、ログインすることでエクスペリエンスが向上する画面で One Tap サインインを使用することを検討してください。たとえば、ユーザーがログアウトした状態でアプリでコンテンツをブラウジングできるものの、コメントの投稿やショッピング カートへの商品の追加はログイン後にのみ行える場合、ワンタップ ログインに適したコンテキストと言えます。

上記の理由から、ログインがオプションのアプリでも、ログイン画面でワンタップ ログインを使用する必要があります。

始める前に

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

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

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

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

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

  • サーバー クライアント ID を Google API コンソールで作成した ID に設定します。これは Android クライアント ID ではなく、サーバーのクライアント 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 を表示する前にユーザーのステータスを確認します。

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

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

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

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

ユーザーに保存された認証情報がない場合、One Tap クライアントは失敗リスナーを呼び出します。この場合、対応は必要ありません。ログアウトした状態のアプリをそのまま表示できます。ただし、ワンタップ登録をサポートしている場合は、ここでそのフローを開始して、シームレスなアカウント作成を実現できます。1 回タップするだけで新しいアカウントを作成するをご覧ください。

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 を閉じたか、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) {
                    // ...
                }
            }
        }
    }
    // ...
}

One Tap 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. ログアウトを処理する

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

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

次のステップ

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

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