首页 > 解决方案 > FirebaseUser 无法重新验证以删除帐户

问题描述

正如标题所示,我无法删除 Firebase 用户。我在 Firebase 控制台中启用了 2 种登录类型:

这些提供程序类型在应用程序中镜像,使用登录不是问题firebase-ui-auth

  listOf(
     IdpConfig.AnonymousBuilder().build(),
     IdpConfig.GoogleBuilder().build())

我希望用户能够删除他们的帐户,这对于匿名用户来说很好,但对于使用GoogleAuthCredential. 为了做到这一点,文档说明您需要“重新验证”:FirebaseUser::reauthenticate。这是我遇到麻烦的地方,重新身份验证总是失败:

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

我想可能是我错误地将令牌添加为参数argumentnt:

GoogleAuthProvider.getCredential(tokenResult.token, null)

所以换成:

GoogleAuthProvider.getCredential(null, tokenResult.token)

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

我在这里做错了什么?

标签: androidfirebasekotlinfirebase-authenticationkotlin-coroutines

解决方案


已解决:最后对这个简单的答案。

两者都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)

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

更新

由于令牌是短暂的,如果令牌变得陈旧,上述方法仍然会失败。解决方案是执行“静默登录”以刷新令牌。这无法完成,FirebaseAuth::silentSignin因为如果 aFirebaseUser已经登录,这将失败。它需要调用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)
}

推荐阅读