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

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

ワンタップ ログイン UI

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

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

ワンタップ ログインはどこで使用できますか?

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

アプリでログインが必須である場合は、ログインすることでエクスペリエンスが強化される画面で、ワンタップ ログインを使用することを検討してください。たとえば、ユーザーがログアウトしている間はアプリのコンテンツを閲覧できても、コメントの投稿やショッピング カートへの商品の追加はログイン後にしか行えない場合、これはワンタップ ログインの妥当なコンテキストと言えます。

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

始める前に

1. ワンタップ ログイン クライアントを設定する

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

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

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

  • サーバーのクライアント ID を Google API Console で作成した ID に設定します。これは、Android クライアント ID ではなく、サーバーのクライアント 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. ログインしているユーザーを確認する

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

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

3. ワンタップ ログイン UI を表示する

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

ユーザーがアプリの認証情報を保存している場合、ワンタップ クライアントは成功リスナーを呼び出します。成功リスナーで、Task の結果からペンディング インテントを取得し、それを startIntentSenderForResult() に渡してワンタップ ログイン UI を起動します。

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

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() のインテント データをワンタップ クライアントの 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 に保存するなど、他の方法を使用することも可能です。

ワンタップ ログイン プロンプトに独自のレート制限を実装することが重要です。追加せずに、ユーザーが複数のプロンプトを続けてキャンセルした場合、ワンタップ クライアントはその後 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 ログインをサポートしている場合は、ワンタップ クライアントを使用して、スムーズなアカウント作成フローをアプリに追加することもできます。