如何阻止同一邮箱通过不同身份提供商重复登录Firebase账号?
问题描述
流程步骤
- 使用Twitter登录
mail@gmail.com(成功) - 使用Google登录
mail@gmail.com(成功将账号关联的提供商改写为Google) - 再次使用Twitter登录
mail@gmail.com(失败,提示账号已关联其他提供商)
我对步骤2的结果感到困惑——Firebase没有阻止使用Google提供商登录同一邮箱,反而直接替换了账号原本关联的提供商。
登录Twitter的代码
Future<Either<Failure, Unit>> signInWithTwitter() async { try { if (kIsWeb) { await _firebaseAuth.signInWithPopup(_twitterAuthProvider); } else { await _firebaseAuth.signInWithProvider(_twitterAuthProvider); } return right(unit); } on FirebaseAuthException catch (e) { return left( Failure.authFailure( message: e.toString(), ), ); } }
登录Google的代码
Future<Either<Failure, Unit>> signInWithGoogle() async { try { final googleUser = await _googleSignIn.signIn(); if (googleUser == null) { return left(const Failure.authFailure()); } final googleAuthentication = await googleUser.authentication; final authCredential = GoogleAuthProvider.credential( idToken: googleAuthentication.idToken, accessToken: googleAuthentication.accessToken, ); await _firebaseAuth.signInWithCredential(authCredential); return right(unit); } on FirebaseAuthException catch (e) { return left( Failure.authFailure( message: e.toString(), ), ); } }
请问如何阻止同一邮箱通过其他身份提供商(如Twitter、GitHub、Facebook等)登录已存在的账号?
解决方案
要实现同一邮箱只能绑定一个身份提供商的限制,需要在登录流程中提前校验邮箱对应的账号是否已关联其他提供商,具体操作如下:
获取登录凭证对应的邮箱
对于Google登录,可直接从googleUser对象中提取邮箱;对于Twitter登录,需在登录后从返回的用户凭证中获取邮箱(注意:Twitter需配置权限才能获取邮箱)。查询邮箱已关联的提供商
使用Firebase Auth的fetchSignInMethodsForEmail方法,传入目标邮箱,获取该邮箱已绑定的所有登录提供商列表。校验提供商匹配性
- 若列表为空:说明邮箱未注册,允许当前提供商登录并关联。
- 若列表不为空:检查当前登录的提供商是否在列表内。不在则直接返回错误,阻止登录;在则正常完成登录流程。
修改后的Google登录代码示例
Future<Either<Failure, Unit>> signInWithGoogle() async { try { final googleUser = await _googleSignIn.signIn(); if (googleUser == null) { return left(const Failure.authFailure(message: "用户取消登录")); } // 获取用户邮箱 final userEmail = googleUser.email; if (userEmail == null) { return left(const Failure.authFailure(message: "无法获取用户邮箱")); } // 查询该邮箱已关联的提供商 final signInMethods = await _firebaseAuth.fetchSignInMethodsForEmail(userEmail); // 校验当前提供商是否匹配 const targetProvider = GoogleAuthProvider.providerId; if (signInMethods.isNotEmpty && !signInMethods.contains(targetProvider)) { return left(const Failure.authFailure(message: "该邮箱已绑定其他登录方式,请使用对应方式登录")); } // 正常完成登录流程 final googleAuthentication = await googleUser.authentication; final authCredential = GoogleAuthProvider.credential( idToken: googleAuthentication.idToken, accessToken: googleAuthentication.accessToken, ); await _firebaseAuth.signInWithCredential(authCredential); return right(unit); } on FirebaseAuthException catch (e) { return left( Failure.authFailure( message: e.toString(), ), ); } }
修改后的Twitter登录代码示例
Future<Either<Failure, Unit>> signInWithTwitter() async { try { UserCredential credential; if (kIsWeb) { credential = await _firebaseAuth.signInWithPopup(_twitterAuthProvider); } else { credential = await _firebaseAuth.signInWithProvider(_twitterAuthProvider); } // 获取用户邮箱 final userEmail = credential.user?.email; if (userEmail == null) { return left(const Failure.authFailure(message: "无法获取用户邮箱,请先绑定邮箱")); } // 查询已关联的提供商 final signInMethods = await _firebaseAuth.fetchSignInMethodsForEmail(userEmail); const targetProvider = TwitterAuthProvider.providerId; if (signInMethods.isNotEmpty && !signInMethods.contains(targetProvider)) { // 校验不通过,先退出临时登录再返回错误 await _firebaseAuth.signOut(); return left(const Failure.authFailure(message: "该邮箱已绑定其他登录方式,请使用对应方式登录")); } return right(unit); } on FirebaseAuthException catch (e) { return left( Failure.authFailure( message: e.toString(), ), ); } }
注意:Twitter登录时会自动创建临时账号,若校验不通过,需调用signOut清除该临时账号,避免残留无效数据。
内容的提问来源于stack exchange,提问作者Afdal
相关产品推荐
相关产品推荐

