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

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

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

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

始める前に

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

  1. まだ追加していない場合は、Firebase を Android プロジェクトに追加します

  2. モジュール(アプリレベル)の Gradle ファイル(通常は <project>/<app-module>/build.gradle.kts または <project>/<app-module>/build.gradle)に、Android 用 Firebase Authentication ライブラリの依存関係を追加します。ライブラリのバージョニングの制御には、Firebase Android BoM を使用することをおすすめします。

    また、Firebase Authentication の設定の一環として、Google Play 開発者サービス SDK をアプリに追加する必要もあります。

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.6.0"))
    
        // Add the dependency for the Firebase Authentication library
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-auth")
    // Also add the dependency for the Google Play services library and specify its version implementation("com.google.android.gms:play-services-auth:21.2.0")
    }

    Firebase Android BoM を使用すると、アプリは常に互換性のあるバージョンの Firebase Android ライブラリを使用します。

    (代替方法)BoM を使用せずに Firebase ライブラリの依存関係を追加する

    Firebase BoM を使用しない場合は、依存関係の行でそれぞれの Firebase ライブラリのバージョンを指定する必要があります。

    アプリで複数の Firebase ライブラリを使用する場合は、すべてのバージョンの互換性を確保するため、BoM を使用してライブラリのバージョンを管理することを強くおすすめします。

    dependencies {
        // Add the dependency for the Firebase Authentication library
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-auth:23.1.0")
    // Also add the dependency for the Google Play services library and specify its version implementation("com.google.android.gms:play-services-auth:21.2.0")
    }
    Kotlin 固有のライブラリ モジュールをお探しの場合、 2023 年 10 月(Firebase BoM 32.5.0)以降、Kotlin と Java のどちらのデベロッパーもメイン ライブラリ モジュールを利用できるようになります(詳しくは、このイニシアチブに関するよくある質問をご覧ください)。

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

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

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

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

    • url: メールに埋め込むディープリンク。状態も一緒に渡します。リンクのドメインは、Firebase コンソールで承認済みドメインの許可リストに登録する必要があります。これは、[Sign-in method] タブ([Authentication] > [Sign-in method])で行えます。アプリがユーザーのデバイスにインストールされていない場合や、アプリをインストールできない場合、この URL にユーザーをリダイレクトします。
    • androidPackageNameIOSBundleId: Android または Apple デバイスでログインリンクを開くアプリ。モバイルアプリ経由でメールのアクション リンクを開く方法については、Firebase Dynamic Links の構成をご覧ください。
    • handleCodeInApp: true に設定します。他の帯域外メール アクション(パスワードのリセットやメールアドレスの確認など)と異なり、ログインはアプリ内で完結させる必要があります。これは、フローの最後でユーザーがログインに成功し、認証状態がアプリ内で維持される必要があるためです。
    • dynamicLinkDomain: 1 つのプロジェクトに複数のカスタム ダイナミック リンク ドメインが定義されている場合、指定のモバイルアプリ経由でリンクを開く際に使用するドメインを指定します(example.page.link など)。指定しない場合、最初のドメインが自動的に選択されます。

    Kotlin+KTX

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

    Java

    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();

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

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

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

    Kotlin+KTX

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

    Java

    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.");
                    }
                }
            });

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

意図しないユーザーやデバイスがログインリンクでログインしないように、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

      受信リンクをインターセプトするように Apple アプリまたは 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 の受信の手順をご覧ください。

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

Kotlin+KTX

val auth = Firebase.auth
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)
            }
        }
}

Java

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());
                    }
                }
            });
}

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

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

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

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

Kotlin+KTX

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

// Link the credential to the current user.
Firebase.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

// 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+KTX

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

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

Java

// 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());
                }
            }
        });

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

2023 年 9 月 15 日以降にプロジェクトを作成した場合、メール列挙保護はデフォルトで有効になっています。この機能により、プロジェクトのユーザー アカウントのセキュリティが強化されますが、以前は ID 優先フローを実装する際に推奨されていた fetchSignInMethodsForEmail() メソッドが無効になります。

プロジェクトでメール列挙保護を無効にすることはできますが、無効にしないことをおすすめします。

詳細については、メール列挙保護に関するドキュメントをご覧ください。

次のステップ

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

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

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

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

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

Kotlin+KTX

Firebase.auth.signOut()

Java

FirebaseAuth.getInstance().signOut();