Reputation: 1873
please help me finding correct solution in the following situation.
I am developing ios app with swift which will use Firebase as a backend.
Users should be able to login into firebase with email/password or/and with facebook. Maybe google will be added later.
It is important for me to have one firebase account for each real user as user will have reward points and it won't make sense if on one devise(with email login) he will have X points and on other device(with facebook) he will have different points.
Here is code which I am using to login with email:
//EMAIL REGISTER
@IBAction func registerAction(_ sender: Any) {
if self.emailTextField.text == "" || self.passwordTextField.text == "" {
Print( "Please enter email and password.")
} else {
FIRAuth.auth()?.createUser(withEmail: self.emailTextField.text!, password: self.passwordTextField.text!, completion: { (user, error) in
if error == nil {
self.logoutButton.isHidden = false
self.usernameLabel.text = user!.email
self.emailTextField.text = ""
self.passwordTextField.text = ""
} else {
Print("\((error?.localizedDescription)!)")
}
} )
}
}
//EMAIL LOGIN
@IBAction func loginAction(_ sender: Any) {
if self.emailTextField.text == "" || self.passwordTextField.text == "" {
print( "Please enter email and password.")
} else {
FIRAuth.auth()?.signIn(withEmail: self.emailTextField.text!, password: self.passwordTextField.text!, completion: { (user, error) in
if error == nil {
self.logoutButton.isHidden = false
self.usernameLabel.text = user!.email
self.emailTextField.text = ""
self.passwordTextField.text = ""
} else {
Print("\((error?.localizedDescription)!)")
}
})
}
}
Here is code which I am using to login with facebook:
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
self.fbLoginButton.isHidden = true
if error != nil {
self.fbLoginButton.isHidden = false
print(error.localizedDescription)
return
} else if (result.isCancelled) {
print("canceled")
self.fbLoginButton.isHidden = false
} else {
let credential = FIRFacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
FIRAuth.auth()?.signIn(with: credential) { (user, error) in
if let error = error {
print(error.localizedDescription)
return
}
}
}
}
Both methods work fine, but they create two separate accounts.
It is set up to allow multiple users with the same email in settings:
and I end up with the following result:
Obviously I want these two accounts to be merged automatically.
Documentation describes the way how to link auth providers, but user should be logged in with one method and only after that it will be possible to link accounts.
linkWithCredential
method is used if you already have the oauth credential. In case user was created on one device with email it wont work if he will decide to login again on other device with facebook.
After loggin in with any of these ways the FIRAuth.auth()?.addStateDidChangeListener
service is showing second view controller where I am working with database.
override func viewDidLoad() { super.viewDidLoad()
let user = FIRAuth.auth()?.currentUser
let dat = user?.providerData
let email = user?.providerData[0].email
let name = user?.displayName
if email != nil {
self.ref.child("user_profile").child("\(user!.uid)/email").setValue(email)
}
if name != {
self.ref.child("user_profile").child("\(user!.uid)/name").setValue(name)
}
}
Let say we loggin with facebook, maybe it is possible to find UID of user with same email and run updates? Not very good idea in case there are a lot of users.
Other idea is to use email as id like this:
self.ref.child("user_profile").child("\(email)/name").setValue(name)
Not sure if it is a good option?
thanks for your replies.
Upvotes: 2
Views: 4610
Reputation: 2238
How to sign in a user from different device after he signed in using he's email and password on the first device without anonymous users.
After creating the user you need to save the email and password to NSUbiquitousKeyValueStore
this way the other device will have access to email and password because you will need to sign in the user first before linking their account to Facebook as you will see below.
In FB console
1. Account Email address settings:Prevent creation of multiple accounts with the same email address This way the account doesn't get created again if the user already signed in with email address.
2.If the user choose email address as first login:
var iCloudKeyStore: NSUbiquitousKeyValueStore = NSUbiquitousKeyValueStore()
FIRAuth.auth()?.createUser(withEmail: email, password: password) { (user, error) in
// User created
if error == nil{
// signed in successfully
//Save the password and the email. Or choose your way to save them
iCloudKeyStore.set(password, forKey: email)
}else{
//Erorr
}
}
4. Now on the other device the user decided to login with Facebook (different provider)
After you have signed in the user with Facebook and have fb's token: Usually inside the function's listener of FBSDKAccessTokenDidChange
Notification, you sign in the user with Firebase with Facebook's token.
let credential = FIRFacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
FIRAuth.auth()?.signIn(with: credential) { (user, error) in
// ...
if error != nil {
// SETP 5. HERE
let nsError = (error as! NSError)
// Error: 17007 The email address is already in use by another account. ERROR_EMAIL_ALREADY_IN_USE
if nsError.code == 17007{
print(error,user?.uid,nsError.code,"facebook user error")
let email = nsError.userInfo["FIRAuthErrorUserInfoEmailKey"] as? String
self.signInUserWithEmail(email: email!)
print("email",email)
}
}else{
print(user!.uid,"facebook user login")
}
Since the user already have an account with this email address which he's now trying to sign in with Facebook, so an error will occurs: as you see in step 4, now you need to sign in the user before you link is account to Facebook:
func signInUserWithEmail(email:String){
let password = iCloudKeyStore.string(forKey: email)
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user:FIRUser?, error:Error?) in
if user != nil{
self.linkAccountWihtFacebook()
print(user?.uid)
}else{
print(error?.localizedDescription)
}
})
}
func linkAccountWihtFacebook(){
let credential = FIRFacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
FIRAuth.auth()?.currentUser?.link(with: credential, completion: { (user:FIRUser?, error:Error?) in
if let LinkedUser = user{
print("NEW USER:",LinkedUser.uid)
}
if let error = error as? NSError{
//Indicates an attempt to link a provider of a type already linked to this account.
if error.code == FIRAuthErrorCode.errorCodeProviderAlreadyLinked.rawValue{
print("FIRAuthErrorCode.errorCodeProviderAlreadyLinked")
}
//This credential is already associated with a different user account.
if error.code == 17025{
}
print("MyError",error)
}
})
}
This way you will have the following result in Firebase console:
Firebase documentation: https://firebase.google.com/docs/auth/ios/account-linking
Link auth provider credentials to a user account
To link auth provider credentials to an existing user account:
1. Sign in the user using any authentication provider or method.
- Complete the sign-in flow for the new authentication provider up to, but not including, calling one of the FIRAuth.signInWith methods. For example, get the user's Google ID token, Facebook access token, or email and password.
- Get a FIRAuthCredential for the new authentication provider
Upvotes: 1