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

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

ワンタップ ログイン UI

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

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

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

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

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

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

始める前に

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

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

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

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

  • サーバー クライアント ID を、Google APIs コンソールで作成した 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. ログイン済みユーザーを確認する

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

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

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

ユーザーがログインしておらず、One Tap サインインの使用をすでに拒否していない場合は、クライアント オブジェクトの 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 サインイン プロンプトに対するユーザーの応答は、Activity の onActivityResult() メソッドを使用してアプリに報告されます。ユーザーがログインを選択した場合、結果は保存された認証情報になります。ユーザーが One Tap 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.CANCELEDApiException をスローします。 この場合、ユーザーに繰り返しプロンプトが表示されて煩わしくならないように、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 ログインをサポートしている場合は、ワンタップ クライアントを使用して、アプリにスムーズなアカウント作成フローを追加 することもできます