Linking multiple Auth Providers using Firebase Auth
In case anyone else runs into the same issue.
It appears there's no way to link Firebase Auth providers 'on the fly' by having the new auth provider that has the same email address as an existing Firebase Auth account via the login flow. Therefore, my solution was to create a 'link facebook to your profile' button in the profile after they have logged in using their email/password. That button then creates the linkage in Firebase to recognize facebook as secondary auth provider for that account. For testing I logged out and used the 'Login with facebook' button and all works as expected.
I think this UX greatly diminishes the utility of the feature but after weeks of research its the best I could come up with.
Code on the Profile to link facebook to email/password original account
@IBAction func fbLoginButton(_ sender: FBButton) {
let loginManager = LoginManager()
loginManager.logIn(permissions: ["public_profile", "email"], from: self) { (result, error) in
if error != nil {
return
}
print(result)
guard let accessToken = AccessToken.current else {
print("Failed to get access token")
return
}
print(accessToken)
let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
print(credential)
if let user = Auth.auth().currentUser {
print("This is the user: \(user)")
// [START link_credential]
user.link(with: credential) { _, error in
// [START_EXCLUDE]
if let error = error {
print("FB did not link to account. \(error)")
return
}
}
}
}
}
Code on Login page (below) email/password login
Don't forget to add the LoginButtonDelegate to the View Controller
@IBAction func fbLoginButton(_ sender: FBButton) {
let loginManager = LoginManager()
loginManager.logIn(permissions: ["public_profile", "email"], from: self) { (result, error) in
if error != nil {
return
}
guard let accessToken = AccessToken.current else {
print("Failed to get access token")
return
}
let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
// ...
return
}
let user = authResult?.user
//Check if user is nill
if user != nil {
//This means that we have a user, now check if they have a User document
UserService.getUserProfile() { (u) in
if u == nil {
//No profile, go to Profile Controller
self.performSegue(withIdentifier: Constants.Segue.profileViewController, sender: self)
}
else {
//Save the logged in user to local storage
LocalStorageService.saveCurrentUser(user: u!)
//This user has a profile, go to tab controller
let tabBarVC = self.storyboard?.instantiateViewController(withIdentifier: Constants.Storyboard.tabBarController)
self.view.window?.rootViewController = tabBarVC
self.view.window?.makeKeyAndVisible()
}
}
}
}
}
}
For the button itself I added a button using storyboard to the View and used the customer class 'FBSDKButton' and styled it accordingly:
let fbButtonText = NSAttributedString(string: "Link facebook to Profile")
fbLoginButton.setAttributedTitle(fbButtonText, for: .normal)
and
let fbButtonText = NSAttributedString(string: "Login with facebook")
fbLoginButton.setAttributedTitle(fbButtonText, for: .normal)
Lastly, be sure you import the appropriate libraries; I used:
import FirebaseAuth
import FBSDKCoreKit
import FacebookCore
import FacebookLogin
import FBSDKLoginKit
import Firebase
Firebase authentication: linking multiple accounts in Swift
So your code follows the first 2 steps in this link. But the documentation explicity says not to call signInWithCredential
but instead call
FIRAuth.auth()?.currentUser.linkWithCredential(credential) { (user, error) in
// ...
}
After getting your credential from Facebook's SDK.
Quote from link: "If the call to linkWithCredential:completion: succeeds, the user's new account can access the anonymous account's Firebase data."
Account linking with different emails
You don't really know they are the same users if their emails differ (anyone can create an email that is similar to another email). So you shouldn't want to build functionality that automatically links the 2 users.
That said, you can manually use the linkWithCredential
API to link the Apple credential to the email/password user provided that user is already signed in, but I think you shouldn't require this unless the email/password user wants to do so (via some button to link their Apple account).
Managing multiple iOS authentication Firebase - Swift - Programmatically
Try this:
if GIDSignIn.sharedInstance()?.handle(url) {
return true
}
else if ApplicationDelegate.shared.application(app, open: url, options: options) {
return true
}
return false
FirebaseUI follows a similar approach - it loops through all available / activated authentication providers, checking for each of them if it is able to handle the URL, and returning true
if that's the case. Check out their implementation.
How to correctly link different Auth accounts in Firebase IOS
When you get the credential already exists error, you already have the email at that point, you then call fetchProvidersForEmail
with that email which will lookup the provider IDs associated with that email. You then sign in the user with one of those providers. After you finish sign-in with the existing account, you call linkWithCredential:completion:
with the original credential that caused the error to occur. This causes the accounts to link. The next time the user tries to sign in, they will be able to sign in to the same user with either provider.
Check FirebaseUI-iOS which already takes care of the whole flow for you. You can also check there source code to see how they handle such situations: https://github.com/firebase/FirebaseUI-iOS
Related Topics
How to Change/Modify The Displayed Title of an Nspopupbutton
Swift3 Different Font in The All of The UIview with Localization Each
Validate Unicode Code Point in Swift
Sklabelnode Text with Two Different Fonts and Colour. How Is This Possible
Pass Data from Tableviewcontroller to Another Tableviewcontroller in Swift
Spacing Between Sections in a Form
What Is The Intended Use of Optional Variable/Constant in Swift
Performance Issue with Swiftui List
Encode Struct and Convert to Dictionary [String: Any]
Difference Between Orientation and Rotation in Scnnode
Heightanchor.Constraint Not Change Height of View
Uialertview or UIalertcontroller to Display Only Once in Swift
It Is Posible to Load Customise HTML View into Webview in Swift
Can't Update Label from Other Class in Swift
Transparency Issues with Repeated Stamping of Textures on an Mtkview