FirebaseUser 无法重新验证以删除帐户
FirebaseUser cannot reauthenitcate to delete account
如标题所示,我在删除 Firebase 用户时遇到问题。我在 Firebase 控制台中启用了 2 种登录类型:
- 匿名
- Google
这些提供者类型在应用程序中有镜像,使用 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 标记的参数是正确的。
我做错了什么?
已解决:简单回答到最后。
FirebaseUser
和GoogleSigInAccount
都指的是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)
}
如标题所示,我在删除 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 标记的参数是正确的。
我做错了什么?
已解决:简单回答到最后。
FirebaseUser
和GoogleSigInAccount
都指的是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)
}