Android でメールリンクを使用して Firebase 認証を行う

Firebase Authentication を使用すると、ログイン用のリンクを含むメールをユーザーに送信し、ログインさせることができます。このプロセスでは、ユーザーのメールアドレスの検証も行います。

メールでのログインには、次のような利点があります。

  • 登録とログインが簡単。
  • アプリ間でパスワードが再利用されるリスクが低い。パスワードを再利用すると、適切なパスワードを選択していても、セキュリティが低下する可能性があります。
  • ユーザー認証で、ユーザーがメールアドレスの正当な所有者であることも確認できる。
  • アクセス可能なメール アカウントがあればログインできる。電話番号やソーシャル メディアのアカウントを所有する必要はありません。
  • パスワードを入力(あるいは記憶)しなくても、安全にログインできる。モバイル端末では、パスワードの入力や記憶が面倒な場合があります。
  • 前にメール ID(パスワードや認証連携)でログインしたユーザーをメールのみによるログインにアップグレードできる。たとえば、パスワードを忘れてしまった場合、パスワードを再設定しなくてもログインできます。

準備

Android Studio プロジェクトを設定する

  1. Firebase を Android プロジェクトに追加します
  2. Firebase Authentication と Google Play 開発者サービスの依存関係をアプリレベルの build.gradle ファイルに追加します。
    implementation 'com.google.firebase:firebase-auth:16.0.4'
    implementation 'com.google.android.gms:play-services-auth:16.0.1'
    

メールリンクでユーザーをログインさせるには、Firebase プロジェクトでメール プロバイダとメールリンクのログイン方法を有効にする必要があります。

  1. Firebase コンソールで [Auth] セクションを開きます。
  2. [ログイン方法] タブで [メール / パスワード] を有効にします。メールリンク ログインを使用するには、メール / パスワードによるログインを有効にする必要があります。
  3. 同じセクションで、ログイン方法として [メールリンク(パスワードなしでログイン)] を有効にします。
  4. [保存] をクリックします。

認証フローを開始するには、ユーザーにメールアドレスの入力を求めるインターフェースを表示します。次に、sendSignInLinkToEmail を呼び出し、ユーザーのメールアドレスに認証リンクを送信するように Firebase にリクエストします。

  1. メールリンクの作成方法を Firebase に伝える ActionCodeSettings オブジェクトを作成します。次のフィールドを設定します。

    • url: メールに埋め込むディープリンク。状態も一緒に渡します。リンクのドメインは、Firebase コンソールで承認済みドメインのホワイトリストに登録する必要があります。これは、[ログイン方法] タブ([Authentication] > [ログイン方法])で行えます。アプリがユーザーの端末にインストールされていない場合や、アプリをインストールできない場合、この URL にユーザーをリダイレクトします。
    • androidPackageNameIOSBundleId: Android または iOS 端末でログインリンクを開くアプリ。モバイルアプリ経由でメールのアクション リンクを開く方法については、Firebase Dynamic Links の構成をご覧ください。
    • handleCodeInApp: true に設定します。パスワードのリセットやメールアドレスの確認など、他の帯域外メール アクションと異なり、ログインはアプリ内で完結させる必要があります。これは、フローの最後でユーザーがログインに成功し、認証状態がアプリ内で維持される必要があるためです。

    Java
    Android

    ActionCodeSettings actionCodeSettings =
            ActionCodeSettings.newBuilder()
                    // URL you want to redirect back to. The domain (www.example.com) for this
                    // URL must be whitelisted in the Firebase Console.
                    .setUrl("https://www.example.com/finishSignUp?cartId=1234")
                    // This must be true
                    .setHandleCodeInApp(true)
                    .setIOSBundleId("com.example.ios")
                    .setAndroidPackageName(
                            "com.example.android",
                            true, /* installIfNotAvailable */
                            "12"    /* minimumVersion */)
                    .build();

    Kotlin
    Android

    val actionCodeSettings = ActionCodeSettings.newBuilder()
            // URL you want to redirect back to. The domain (www.example.com) for this
            // URL must be whitelisted in the Firebase Console.
            .setUrl("https://www.example.com/finishSignUp?cartId=1234")
            // This must be true
            .setHandleCodeInApp(true)
            .setIOSBundleId("com.example.ios")
            .setAndroidPackageName(
                    "com.example.android",
                    true, /* installIfNotAvailable */
                    "12"    /* minimumVersion */)
            .build()

    ActionCodeSettings の詳細については、メール アクションで状態を渡すをご覧ください。

  2. ユーザーにメールアドレスを求めます。

  3. ユーザーのメールアドレスに認証リンクを送信し、ユーザーが同じ端末でメールによるログインを完了する場合に備えてこのメールアドレスを保存します。

    Java
    Android

    FirebaseAuth auth = FirebaseAuth.getInstance();
    auth.sendSignInLinkToEmail(email, actionCodeSettings)
            .addOnCompleteListener(new OnCompleteListener<Void>() {
                @Override
                public void onComplete(@NonNull Task<Void> task) {
                    if (task.isSuccessful()) {
                        Log.d(TAG, "Email sent.");
                    }
                }
            });

    Kotlin
    Android

    val auth = FirebaseAuth.getInstance()
    auth.sendSignInLinkToEmail(email, actionCodeSettings)
            .addOnCompleteListener { task ->
                if (task.isSuccessful) {
                    Log.d(TAG, "Email sent.")
                }
            }

セキュリティに関する懸念

意図しないユーザーや端末がログインリンクでログインしないように、Firebase Auth はログインフローの完了時にユーザーにメールアドレスの入力を求めます。入力されたメールアドレスがログインリンクの送信アドレスと一致しないと、ログインに失敗します。

ログインメールの送信時に、SharedPreferences などを使用してメールアドレスをローカルに保存しておくことで、リンクをリクエストした同じ端末でログインリンクを開くユーザーの認証フローが簡単になります。メールアドレスが一致した場合、このアドレスを使用してフローを完了します。セッション インジェクションが可能になるため、ユーザーのメールアドレスをリダイレクト URL パラメータに渡して再利用してはなりません。

ログインが完了すると、未確認のログインはすべて破棄され、既存のセッションが無効になります。たとえば、未確認のアカウントが同じメールとパスワードですでに作成されている場合、所有者を装って未確認アカウントを作成した人物が未確認のメールアドレスとパスワードでログインできないように、ユーザーのパスワードが削除されます。

中間のサーバーでリンクがインターセプトされないように、本番環境では HTTPS URL を使用してください。

Android アプリでログインを完了する

Firebase Authentication は、Firebase Dynamic Links を使用してモバイル端末にメールリンクを送信します。モバイルアプリ経由でログインを行う場合、リンクの受信を検出し、ディープリンクを解析してログインを完了するようにアプリを構成する必要があります。

Firebase Auth では、モバイルアプリで開かれるリンクを送信するときに Firebase Dynamic Links が使用されます。この機能を使用するには、Firebase コンソールで Dynamic Links を構成する必要があります

  1. Firebase Dynamic Links を有効にします。

    1. Firebase コンソールで [Dynamic Links] セクションを開きます。
    2. Dynamic Links の利用規約をまだ受け入れておらず、また Dynamic Links のドメインをまだ作成していない場合は、この時点でその作業を行います。

      Dynamic Links のドメインが作成済みである場合は、それをメモしておきます。Dynamic Links のドメインは、通常、次の例のようになります。

      example.page.link

      受信リンクをインターセプトするように iOS または Android アプリを構成するときに、この値が必要になります。

  2. Android アプリを構成します。

    1. これらのリンクを Android アプリで処理するには、Firebase コンソールのプロジェクト設定で Android パッケージ名を指定する必要があります。さらに、アプリ証明書の SHA-1 および SHA-256 を指定する必要があります。
    2. これでダイナミック リンクのドメインが追加され、Android アプリが正しく構成されていることが確認されました。ランチャー アクティビティの初めから、ダイナミック リンクがアプリケーションにリダイレクトされます。
    3. ダイナミック リンクを特定のアクティビティにリダイレクトする場合は、AndroidManifest.xml ファイルにインテント フィルタを構成する必要があります。これを行うには、インテント フィルタでダイナミック リンクドメインまたはメール アクション ハンドラを指定します。デフォルトでは、メール アクション ハンドラは次のようなドメインでホストされます。
      PROJECT_ID.firebaseapp.com/
    4. 注意点:
      1. actionCodeSettings で設定した URL をインテント フィルタに指定しないでください。
      2. ダイナミック リンクドメインを作成するときに、短縮 URL リンクを作成することもできますが、この短縮 URL は渡されません。android:pathPrefix 属性でキャッチするようにインテント フィルタを構成しないでください。アプリケーションの異なる部分にある別のダイナミック リンクはキャッチできません。ただし、実行されるオペレーションをリンクの mode クエリ パラメータで確認できます。また、isSignInWithEmailLink などの SDK メソッドを使用して、アプリが受信したリンクが必要な処理を行うかどうか確認できます。
    5. ダイナミック リンクの受信の詳細については、Android Dynamic Links の受信の手順をご覧ください。

上記のリンクを受信したら、このリンクがメールリンク認証用であることを確認して、ログインを完了します。

Java
Android

FirebaseAuth auth = FirebaseAuth.getInstance();
Intent intent = getIntent();
String emailLink = intent.getData().toString();

// Confirm the link is a sign-in with email link.
if (auth.isSignInWithEmailLink(emailLink)) {
    // Retrieve this from wherever you stored it
    String email = "someemail@domain.com";

    // The client SDK will parse the code from the link for you.
    auth.signInWithEmailLink(email, emailLink)
            .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        Log.d(TAG, "Successfully signed in with email link!");
                        AuthResult result = task.getResult();
                        // You can access the new user via result.getUser()
                        // Additional user info profile *not* available via:
                        // result.getAdditionalUserInfo().getProfile() == null
                        // You can check if the user is new or existing:
                        // result.getAdditionalUserInfo().isNewUser()
                    } else {
                        Log.e(TAG, "Error signing in with email link", task.getException());
                    }
                }
            });
}

Kotlin
Android

val auth = FirebaseAuth.getInstance()
val intent = intent
val emailLink = intent.data!!.toString()

// Confirm the link is a sign-in with email link.
if (auth.isSignInWithEmailLink(emailLink)) {
    // Retrieve this from wherever you stored it
    val email = "someemail@domain.com"

    // The client SDK will parse the code from the link for you.
    auth.signInWithEmailLink(email, emailLink)
            .addOnCompleteListener { task ->
                if (task.isSuccessful) {
                    Log.d(TAG, "Successfully signed in with email link!")
                    val result = task.result
                    // You can access the new user via result.getUser()
                    // Additional user info profile *not* available via:
                    // result.getAdditionalUserInfo().getProfile() == null
                    // You can check if the user is new or existing:
                    // result.getAdditionalUserInfo().isNewUser()
                } else {
                    Log.e(TAG, "Error signing in with email link", task.exception)
                }
            }
}

iOS アプリでメールリンクによるログインを処理する方法については、iOS のガイドをご覧ください。

ウェブアプリでメールリンクによるログインを処理する方法については、ウェブのガイドをご覧ください。

この認証方法は既存ユーザーの認証でも利用できます。たとえば、ユーザーが電話番号などの別のプロバイダで認証を行っている場合、既存のアカウントにメールリンクによる認証方法を追加できます。

オペレーションの後半部分が異なります。

Java
Android

// Construct the email link credential from the current URL.
AuthCredential credential =
        EmailAuthProvider.getCredentialWithLink(email, emailLink);

// Link the credential to the current user.
auth.getCurrentUser().linkWithCredential(credential)
        .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                if (task.isSuccessful()) {
                    Log.d(TAG, "Successfully linked emailLink credential!");
                    AuthResult result = task.getResult();
                    // You can access the new user via result.getUser()
                    // Additional user info profile *not* available via:
                    // result.getAdditionalUserInfo().getProfile() == null
                    // You can check if the user is new or existing:
                    // result.getAdditionalUserInfo().isNewUser()
                } else {
                    Log.e(TAG, "Error linking emailLink credential", task.getException());
                }
            }
        });

Kotlin
Android

// Construct the email link credential from the current URL.
val credential = EmailAuthProvider.getCredentialWithLink(email, emailLink)

// Link the credential to the current user.
auth.currentUser!!.linkWithCredential(credential)
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                Log.d(TAG, "Successfully linked emailLink credential!")
                val result = task.result
                // You can access the new user via result.getUser()
                // Additional user info profile *not* available via:
                // result.getAdditionalUserInfo().getProfile() == null
                // You can check if the user is new or existing:
                // result.getAdditionalUserInfo().isNewUser()
            } else {
                Log.e(TAG, "Error linking emailLink credential", task.exception)
            }
        }

これは、重要なオペレーションの前にメールリンク ユーザーを再認証する場合にも使用できます。

Java
Android

// Construct the email link credential from the current URL.
AuthCredential credential =
        EmailAuthProvider.getCredentialWithLink(email, emailLink);

// Re-authenticate the user with this credential.
auth.getCurrentUser().reauthenticateAndRetrieveData(credential)
        .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                if (task.isSuccessful()) {
                    // User is now successfully reauthenticated
                } else {
                    Log.e(TAG, "Error reauthenticating", task.getException());
                }
            }
        });

Kotlin
Android

// Construct the email link credential from the current URL.
val credential = EmailAuthProvider.getCredentialWithLink(email, emailLink)

// Re-authenticate the user with this credential.
auth.currentUser!!.reauthenticateAndRetrieveData(credential)
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                // User is now successfully reauthenticated
            } else {
                Log.e(TAG, "Error reauthenticating", task.exception)
            }
        }

ただし、元のユーザーがログインしていない別の端末でフローを完了しようとすると、フローが完了できなくなります。この場合、ユーザーにエラーを表示し、同じ端末で強制的にリンクを開かせます。オペレーションの種類やユーザーの UID の情報を提供するため、リンクと一緒に状態を渡すこともできます。

メールのパスワードとリンクベースの両方のログイン方法をサポートする場合、fetchSignInMethodsForEmail を使用して、それぞれのログイン方法を区別します。この方法は、ユーザーにメールアドレスの入力を求め、入力後にログイン方法を提示する ID 優先のフローで役立ちます。

Java
Android

auth.fetchSignInMethodsForEmail(email)
        .addOnCompleteListener(new OnCompleteListener<SignInMethodQueryResult>() {
            @Override
            public void onComplete(@NonNull Task<SignInMethodQueryResult> task) {
                if (task.isSuccessful()) {
                    SignInMethodQueryResult result = task.getResult();
                    List<String> signInMethods = result.getSignInMethods();
                    if (signInMethods.contains(EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD)) {
                        // User can sign in with email/password
                    } else if (signInMethods.contains(EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD)) {
                        // User can sign in with email/link
                    }
                } else {
                    Log.e(TAG, "Error getting sign in methods for user", task.getException());
                }
            }
        });

Kotlin
Android

auth.fetchSignInMethodsForEmail(email)
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                val result = task.result
                val signInMethods = result.signInMethods
                if (signInMethods!!.contains(EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD)) {
                    // User can sign in with email/password
                } else if (signInMethods.contains(EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD)) {
                    // User can sign in with email/link
                }
            } else {
                Log.e(TAG, "Error getting sign in methods for user", task.exception)
            }
        }

上記のように、メール / パスワードとメールリンクは、異なるログイン方法を使用する同じ EmailAuthProvider(同じ PROVIDER_ID)と考えることができます。

次のステップ

ユーザーが初めてログインすると、新しいユーザー アカウントが作成され、ユーザーがログインに使用した認証情報(ユーザー名とパスワード、電話番号、または認証プロバイダ情報)にアカウントがリンクされます。この新しいアカウントは Firebase プロジェクトの一部として保存され、ユーザーのログイン方法にかかわらず、プロジェクトのすべてのアプリでユーザーを識別するために使用できます。

  • アプリでは、FirebaseUser オブジェクトからユーザーの基本的なプロフィール情報を取得できます。ユーザーの管理についての記事をご覧ください。

  • Firebase Realtime Database と Cloud Storage のセキュリティ ルールでは、ログイン済みユーザーの一意のユーザー ID を auth 変数から取得し、それを使用して、ユーザーがアクセス可能なデータを制御できます。

既存のユーザー アカウントに認証プロバイダの認証情報をリンクすることで、ユーザーが複数の認証プロバイダを使用してアプリにログインできるようになります。

ユーザーのログアウトを行うには、signOut を呼び出します。

Java
Android

FirebaseAuth.getInstance().signOut();

Kotlin
Android

FirebaseAuth.getInstance().signOut()

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。