在 Android 中使用電子郵件連結進行 Firebase 驗證

您可以使用 Firebase 驗證讓使用者登入,方法是傳送含有連結的電子郵件,使用者只需按一下該連結即可登入。在過程中,也會驗證使用者的電子郵件地址。

使用電子郵件登入的眾多好處如下:

  • 簡化的註冊和登入流程。
  • 可降低跨應用程式重複使用密碼的風險,避免選得好的密碼。
  • 驗證使用者的同時,還能驗證使用者是否為電子郵件地址的合法擁有者。
  • 使用者只需能夠存取的電子郵件帳戶即可登入。使用者不需要電話號碼或社群媒體帳戶。
  • 使用者無需提供 (或記住) 密碼,就能安全登入。使用行動裝置時,可能相當麻煩。
  • 先前曾以電子郵件 ID (密碼或聯合) 登入的現有使用者,可以升級以僅以電子郵件登入。舉例來說,如果使用者忘記密碼,仍然可以登入,而不必重設密碼。

事前準備

設定 Android 專案

  1. 如果您尚未將 Firebase 新增至 Android 專案,請先完成這項作業。

  2. 模組 (應用程式層級) Gradle 檔案 (通常是 <project>/<app-module>/build.gradle.kts<project>/<app-module>/build.gradle) 中,新增 Android 適用的 Firebase 驗證程式庫的依附元件。建議您使用 Firebase Android BoM 來控管程式庫版本管理。

    此外,設定 Firebase 驗證時,您需要將 Google Play 服務 SDK 新增至應用程式。

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.1.2"))
    
        // 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 程式庫版本。

    (替代方法) 新增 Firebase 程式庫依附元件,「不必」使用 BoM

    如果選擇不使用 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.0.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」分頁中,啟用「Email/Password」供應商。請注意,您必須啟用電子郵件/密碼登入才能使用電子郵件連結登入。
  3. 在同一部分中,啟用「電子郵件連結 (無密碼登入)」登入方式。
  4. 點選「Save」

如要啟動驗證流程,請向使用者顯示介面,藉此提示使用者提供電子郵件地址,然後呼叫 sendSignInLinkToEmail 要求 Firebase 將驗證連結傳送至使用者的電子郵件地址。

  1. 建構 ActionCodeSettings 物件,為 Firebase 提供建構電子郵件連結的操作說明。設定下列欄位:

    • url:要嵌入的深層連結,以及任何要隨之傳遞的其他狀態。連結的網域必須列在 Firebase 控制台的授權網域許可清單中 (如要查看,請前往「登入方式」分頁 (「驗證」->「登入方式」)。如果使用者的裝置未安裝該應用程式,且應用程式無法安裝,則連結會將使用者重新導向至這個網址。
    • androidPackageNameIOSBundleId:在 Android 或 Apple 裝置上開啟登入連結時使用的應用程式。進一步瞭解如何設定 Firebase Dynamic Links,以便透過行動應用程式開啟電子郵件動作連結。
    • handleCodeInApp:設為 true。登入作業必須一律在應用程式中完成,這與錶帶電子郵件以外的操作 (密碼重設和電子郵件驗證) 不同。這是因為流程結束時,使用者會預期使用者處於登入狀態,且應用程式的驗證狀態會保留在應用程式中。
    • dynamicLinkDomain:為專案定義多個自訂動態連結網域時,請指定透過指定的行動應用程式 (例如 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 驗證會在完成登入流程時要求提供使用者的電子郵件地址。這個電子郵件地址必須與最初傳送登入連結的地址一致,才能成功登入。

當使用者在要求連結的裝置上開啟登入連結時,您可以將他們的電子郵件地址儲存在本機 (例如傳送登入電子郵件時採用 SharedPreferences),藉此簡化這個流程。接著,請使用這個地址完成流程。請不要在重新導向網址參數中傳遞使用者的電子郵件,然後再重新使用,因為這樣可能會啟動工作階段插入。

登入完成後,系統會移除使用者中所有未經驗證的登入機制,任何現有的工作階段都會失效。舉例來說,如果某位使用者先前使用相同的電子郵件地址和密碼建立未經驗證的帳戶,使用者的密碼將會遭到移除,該身分的使用者可能會失去聲明擁有權並建立未經驗證帳戶,而無法以未經驗證的電子郵件和密碼重新登入。

此外,請務必在實際工作環境中使用 HTTPS 網址,以免中介伺服器攔截您的連結。

在 Android 應用程式中完成登入

Firebase 驗證會使用 Firebase Dynamic Links 將電子郵件連結傳送至行動裝置。如要透過行動應用程式完成登入程序,應用程式必須設為偵測傳入的應用程式連結、剖析基礎深層連結,然後完成登入。

Firebase 驗證會在傳送要在行動應用程式中開啟的連結時,使用 Firebase Dynamic Links。如要使用這項功能,您「必須」在 Firebase 控制台中設定 Dynamic Links。

  1. 啟用 Firebase Dynamic Links:

    1. Firebase 控制台開啟「Dynamic Links」部分。
    2. 如果您尚未接受動態連結條款並建立 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 中指定網址。
      2. 建立動態連結網域時,您也可以建立一個短網址連結。系統不會傳遞這個短網址;請勿設定意圖篩選器,以便使用 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 日當天或之後建立專案,系統預設會啟用電子郵件列舉防護功能。這項功能可改善專案使用者帳戶的安全性,但停用了 fetchSignInMethodsForEmail() 方法,先前建議用於實作以 ID 為優先的流程。

雖然您可以停用專案的電子郵件列舉防護功能,但我們不建議這麼做。

詳情請參閱電子郵件列舉防護說明文件。

後續步驟

使用者首次登入後,系統會建立新的使用者帳戶,並連結至用來登入的使用者憑證,也就是使用者名稱與密碼、電話號碼或驗證提供者資訊。這個新帳戶以 Firebase 專案的形式儲存,無論使用者以何種方式登入,都能從專案中的每個應用程式識別使用者。

  • 在應用程式中,您可以從 FirebaseUser 物件取得使用者的基本個人資料。請參閱「 管理使用者」一文。

  • 在 Firebase 即時資料庫和 Cloud Storage 安全性規則中,您可以從 auth 變數取得已登入使用者的專屬 ID,並使用該 ID 控管使用者可存取的資料。

您可以將驗證提供者憑證連結至現有的使用者帳戶,讓使用者透過多個驗證提供者登入您的應用程式。

如要登出使用者,請呼叫 signOut

Kotlin+KTX

Firebase.auth.signOut()

Java

FirebaseAuth.getInstance().signOut();