So, this is the solution that I've always worked with and it is mentioned in the Firebase Documentation itself. First of all, you need to understand the concept of linking Multiple Auth Providers. I'm trying to be as elaborative as possible as a lot of you are facing this issue. Firebase is owned by Google and every time you sign in with Google it automatically overrides the other sign-in methods like Apple Sign In, Facebook, Google.
So, to solve that use the Google Sign In button. I'm keeping my answer generic even the Javascript developers can follow this as I've done the same thing in JS as well.
You have to get the email from Google and every provider like FB, Apple etc. Then you have to use this method: fetchSignInMethods.
func fetchSignInMethods(provider: String, email: String) {
Auth.auth().fetchSignInMethods(forEmail: email) { (providers, error) in
if let error = error {
print(error)
return
}
let matched = providers?.filter{ $0 == provider }.count //Here I check if the provider I sent example `Facebook` and the fetched provider are same or not, if it is matched then I can go ahead and login the user directly else I have to show them linking alert as already this email exists with different sign in methods
if providers?.isEmpty || matched == 1 {
var credential: AuthCredential!
switch provider {
case Constants.LoginProviders.Google.rawValue: //google.com
credential = GoogleAuthProvider.credential(withIDToken: idToken,accessToken: token) //idToken and AccessToken are fetched from Google Sign In Button
default: //facebook.com
credential = FacebookAuthProvider.credential(withAccessToken: token) //AccessToken is fetched from Facebook Sign In Button
break
}
self.loginWithCredential(credentail: credential, provider: provider)
} //I am also checking if provider is empty then I can sign them up for Email and Password users using Auth.auth().createUserWithEmailAndPassword
else {
self.displayLinkingAlert(provider: fetchedProviderName)
}
}
}
//This function is used to display the alert when an account needs to be linked
//Provider is the parameter which will be fetched from LoginProviders(Constants.swift)
private func displayLinkingAlert(provider: String) {
MKProgress.hide()
let providerName = getProviderName(provider: provider)
let alertC: UIAlertController = UIAlertController(title: "Link Accounts?", message: "This account is already linked with \(providerName). Do you want to link accounts?", preferredStyle: .alert)
let linkAction: UIAlertAction = UIAlertAction(title: "Link account with \(providerName)", style: .default) { (_) in
self.linkAccounts.toggle() //Toggle linkAccounts to true to link the accounts of user
switch provider {
case Constants.LoginProviders.Google.rawValue:
GIDSignIn.sharedInstance()?.signIn()
break
default: self.facebookLoginWithPermissions(from: LoginViewController())
}
}
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let actions: [UIAlertAction] = [linkAction, cancelAction]
actions.forEach{ alertC.addAction($0) }
UIApplication.topViewController()?.present(alertC, animated: true, completion: nil)
} //I also too a private var linkAccounts = false above in the class
//This function is used to get the providerName from the provider
//Ex.:- provider -> google.com, providerName: Google
private func getProviderName(provider: String) -> String
{
var providerName = ""
switch provider
{
case Constants.LoginProviders.Email.rawValue : providerName = "Email"
case Constants.LoginProviders.Facebook.rawValue : providerName = "Facebook"
case Constants.LoginProviders.Google.rawValue : providerName = "Google"
default: break
}
return providerName
}
Now, this will show you the linking alert. Well, it is up to you how you want to proceed. In my apps, I've Google, Facebook and Email sign in methods enabled and I show the users both of them in case if two providers are fetched from Firebase. For eg.
If I have already signed up with Google and Facebook, then if I try to sign the user up with email it will show me two alert buttons Link With Google and Link With Facebook. Now, it is up to you if you want you can show them both or you can just them one, I can even add Apple Sign In here, it's up to individual's choice.
Now, when the alert appears and you press the Link With Facebook option firstly it will toggle the linkAccount variable to true and then, it will call the facebookTap method which will again fetch the providers like this:
private func facebookTap() {
//Get the details and get the email then...
fetchSignInMethods(provider: Constants.LoginProviders.Facebook.rawValue //facebook.com, email: emailFetched) //This will sign in the user using Facebook and as the linkAccount variable is true it will link ther user
}
//A common function used to login the users, by using their credentails
//Parameter credential is of type AuthCredential which is used to
//signInAndRetrieve data of a particular user.
func loginWithCredential(credentail: AuthCredential, provider: String) {
authInstance.signInAndRetrieveData(with: credentail) { [unowned self](authResult, error) in
if let error = error {
print(error)
} else {
if self.linkAccounts {
var loginCredential: AuthCredential!
switch provider {
case Constants.LoginProviders.Google.rawValue:
guard let facebookTokenString = AccessToken.current?.tokenString else { return }
loginCredential = FacebookAuthProvider.credential(withAccessToken: facebookTokenString)
break
default:
loginCredential = GoogleAuthProvider.credential(withIDToken: self.googleIdToken, accessToken: self.googleAccessToken)
break
}
self.linkAccounts(credential: loginCredential)
} else {
self.getFirebaseToken()
}
}
}
}
Now, I am using this to link accounts:
//This function is used to link the Accounts
//Ex:- Google with Facebook, vice-versa
private func linkAccounts(credential: AuthCredential) {
authInstance.currentUser?.link(with: credential, completion: { (authResult, error) in
if let error = error {
print(error)
return
} else {
self.linkAccounts.toggle() //Toggle Link Account to false as acccounts are already linked
//Navigate user to another page or do your stuff
}
})
}
Now, this is the solution that I tried and it worked for me everytime. It worked for me in Swift, Flutter, Javascript and it will work with every other language. You can do the same thing with Apple Sign In Button. In the delegate where you get the details if user shared their email address then you can use fetchSignInMethods and it will automatically show with the above alert. I've not got the chance to link the user who chose to keep their identity secret, but I will update my answer with that very soon.
I've tried to be as elaborative as possible, please do let me know if you have any queries.
