FirebaseUser 无法重新验证以删除帐户

FirebaseUser cannot reauthenitcate to delete account

如标题所示,我在删除 Firebase 用户时遇到问题。我在 Firebase 控制台中启用了 2 种登录类型:

这些提供者类型在应用程序中有镜像,使用 firebase-ui-auth

登录不是问题
  listOf(
     IdpConfig.AnonymousBuilder().build(),
     IdpConfig.GoogleBuilder().build())

我希望用户能够删除他们的帐户,这对匿名用户来说效果很好,但对于使用 GoogleAuthCredential 使用 google 帐户登录的用户则失败了。为此,文档说明您需要“重新验证”:FirebaseUser::reauthenticate。这是我遇到麻烦的地方,re-authentication 总是失败:

FirebaseAuthInvalidCredentialsException

ERROR_INVALID_CREDENTIAL
       
The supplied auth credential is malformed or has expired. [ Invalid id_token in IdP response: <token provided in request>, error: id token is not issued by Google. ]

我已检查令牌在 UTC 到期时间内,我的设备时钟设置正确。

当前代码(使用协程):

class UserActions internal constructor(
        private val context: Context,
        private val authUI: AuthUI,
        private val auth: FirebaseAuth)  {

    suspend fun signOut(): Boolean = suspendCoroutine { cont -> cont.suspendCompletableTask(authUI.signOut(context)) }

    suspend fun delete(): Boolean {
        auth.currentUser
            ?.takeIf { user -> !user.isAnonymous }
            ?.let { user ->
                val tokenResult: GetTokenResult = suspendCoroutine { cont -> cont.suspendTask(user.getIdToken(true)) }
                val credential : AuthCredential = GoogleAuthProvider.getCredential(tokenResult.token, null)
                // Point of failure - always returns false with above error.
                val success: Boolean = suspendCoroutine { cont -> cont.suspendCompletableTask(user.reauthenticate(credential)) }
                if (!success) return false
        }

        return suspendCoroutine { cont -> cont.suspendCompletableTask(authUI.delete(context)) }
    }

    private fun <R> Continuation<R>.suspendTask(task: Task<R>) {
        task.addOnSuccessListener { this.success(it) }
            .addOnFailureListener { this.failure(it) }
    }

    private fun Continuation<Boolean>.suspendCompletableTask(task: Task<Void>) {
        task.addOnSuccessListener { this.success() }
            .addOnFailureListener { this.failure() }
    }
    
    private fun Continuation<Boolean>.success() = resume(true)
    private fun Continuation<Boolean>.failure() = resume(false)

    private fun <R> Continuation<R>.success(r : R) = resume(r)
    private fun <R> Continuation<R>.failure(t : Exception) = resumeWithException(t)
}

我想也许我错误地添加了令牌作为 arguemnt 的参数:

GoogleAuthProvider.getCredential(tokenResult.token, null)

所以换成:

GoogleAuthProvider.getCredential(null, tokenResult.token)

但是说我在错误描述中有一个无效值,所以据我所知,AuthCredential 和“有效”id 标记的参数是正确的。

我做错了什么?

已解决:简单回答到最后。

FirebaseUserGoogleSigInAccount都指的是idToken。我在这里使用前者作为令牌:

val tokenResult: GetTokenResult = suspendCoroutine { cont -> cont.suspendTask(user.getIdToken(true)) }
val credential : AuthCredential = GoogleAuthProvider.getCredential(tokenResult.token, null)

AuthCredential 现在使用了不正确的标记。我应该使用的是:

val token = GoogleSignIn.getLastSignedInAccount(context)?.idToken.orEmpty()
val credential : AuthCredential = GoogleAuthProvider.getCredential(token, null)

所以错误是正确的 - 我在重新验证应该使用 GoogleSigInAccount 令牌的 GoogleAuthCredential 时使用了 FirebaseUser 令牌。

更新

由于令牌的寿命很短,因此如果令牌过时,上述方法仍然会失败。解决方案是执行“静默登录”以刷新令牌。 不能 通过 FirebaseAuth::silentSignin 完成,因为如果 FirebaseUser 已经登录,这将失败。它需要调用 GoogleSignInClient::silentSignIn.

修改后的完整代码:

class UserActions internal constructor(
    private val context: Context,
    private val authUI: AuthUI,
    private val auth: FirebaseAuth) {

    suspend fun signOut(): Boolean = suspendCoroutine { cont -> cont.suspendCompletableTask(authUI.signOut(context)) }

    suspend fun delete(): Boolean {
        auth.currentUser
            ?.takeIf { user -> !user.isAnonymous }
            ?.let { user ->
                val credential: AuthCredential = GoogleAuthProvider.getCredential(getFreshGoogleIdToken(), null)
                val success: Boolean = suspendCoroutine { cont -> cont.suspendCompletableTask(user.reauthenticate(credential)) }
                if (!success) return false
            }

        return suspendCoroutine { cont -> cont.suspendCompletableTask(authUI.delete(context)) }
    }

    private suspend fun getFreshGoogleIdToken(): String = suspendCoroutine<GoogleSignInAccount> { cont ->
        cont.suspendTask(
            GoogleSignIn.getClient(
                context,
                GoogleSignInOptions.Builder()
                    .requestProfile()
                    .requestId()
                    .requestIdToken(context.getString(R.string.default_web_client_id))
                    .build())
                .silentSignIn())
    }.idToken.orEmpty()

    private fun <R> Continuation<R>.suspendTask(task: Task<R>) {
        task.addOnSuccessListener { this.success(it) }
            .addOnFailureListener { this.failure(it) }
    }

    private fun Continuation<Boolean>.suspendCompletableTask(task: Task<Void>) {
        task.addOnSuccessListener { this.success() }
            .addOnFailureListener { this.failure() }
    }

    private fun Continuation<Boolean>.success() = resume(true)
    private fun Continuation<Boolean>.failure() = resume(false)

    private fun <R> Continuation<R>.success(r: R) = resume(r)
    private fun <R> Continuation<R>.failure(t: Exception) = resumeWithException(t)
}