When a user logs into the app using Facebook, I am able to capture and display their full name; however, neither email nor phone number is coming across. I have tried both the "One account per email address" as well as "Multiple accounts per email". I have tested it with an account whose email address is definitely not already registered in Firebase. What am I missing such that email/phone are not being captured? This all does work with Google accounts.
let name = Auth.auth().currentUser?.displayName // works!
let email = Auth.auth().currentUser?.email // nil- why?
let phone = Auth.auth().currentUser?.phoneNumber // nil -why?
The login process, which is standard Firebase w/ Facebook, looks like this:
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!)
{
if let error = error
{
print(error.localizedDescription)
}
else
{
if FBSDKAccessToken.current() != nil
{
let credential = FacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
Auth.auth().signIn(with: credential) { (user, error) in
if let error = error
{
print (error.localizedDescription)
}
}
}
}
}
To update based on comments below. The following also produces a nil email and phone when inspecting the contents of userInfo. I understand the phone might just be that way, but it seems the email was supposed to work.
let userInfo = Auth.auth().currentUser?.providerData
The top level phoneNumber currentUser.phoneNumber is only for Firebase verified phone numbers. If you have that Facebook phone number, you can use the currentUser.updatePhoneNumber API to verify that number (you will need to go through the whole flow). Otherwise, you can wait for the upcoming Admin node.js API to set phone numbers with Admin privileges on existing users: https://github.com/firebase/firebase-admin-node/commit/68563c4b2c8128fbc45fc65bad3f6730d320b539
As for the email, in the case of "multiple accounts per email" you need to set it yourself via currentUser.updateEmail. You can get the Facebook email from currentUser.providerData which contains the Facebook provider data.
Related
After many tests I decided to create a new xCode project to better understand Firebase authentication with multiple providers.
I set up in Firebase -> SignIn Methods -> An account per email address
An account per email address
Prevents users from creating multiple
accounts using the same email address with different authentication
providers
At this point I have implemented, carefully following the Firebase guide, the login with Facebook and with Google .. Everything seems to work perfectly but I always find myself with the same error that I can't manage:
When my user creates a Firebase account via Google he is no longer able to log in if he decides to use Facebook.
Facebook returns its error when it completes its authentication flow with Firebase:
Firebase Error With Facebook Provider: An account already exists with the same email address but different sign-in credentials. Sign in using a provider associated with this email address.
Continuing to follow the documentation step by step I stopped here (firebase explains how to handle this error)
I have also implemented error handling but after calling Auth.auth().fetchSignInMethods Firebase says I should authenticate the user with the existing provider, at this point how do I get the credentials for authentication with the existing provider?
I wouldn't want to reopen the existing provider controller to get new credentials
Am I obliged to ask the user to log in with the existing provider and show another access controller again (in this case that of Google)?
How should I handle this situation?
override func viewDidLoad() {
super.viewDidLoad()
facebookSetup()
}
func facebookSetup() {
let loginButton = FBLoginButton(permissions: [ .publicProfile, .email ])
loginButton.center = view.center
loginButton.delegate = self
view.addSubview(loginButton)
}
//MARK: - FACEBOOK Delegate
func loginButton(_ loginButton: FBLoginButton, didCompleteWith result: LoginManagerLoginResult?, error: Error?) {
if let error = error {
print(error.localizedDescription)
return
}
let credential = FacebookAuthProvider.credential(withAccessToken: AccessToken.current!.tokenString)
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print("\n FIREBASE: ",error.localizedDescription)
// An account with the same email already exists.
if (error as NSError?)?.code == AuthErrorCode.accountExistsWithDifferentCredential.rawValue {
// Get pending credential and email of existing account.
let existingAcctEmail = (error as NSError).userInfo[AuthErrorUserInfoEmailKey] as! String
let pendingCred = (error as NSError).userInfo[AuthErrorUserInfoUpdatedCredentialKey] as! AuthCredential
// Lookup existing account identifier by the email.
Auth.auth().fetchSignInMethods(forEmail: existingAcctEmail) { providers, error in
if (providers?.contains(GoogleAuthProviderID))! {
// Sign in with existing account.
Auth.auth().signIn(with: "? ? ? ?") { user, error in
// Successfully signed in.
if user != nil {
// Link pending credential to account.
Auth.auth().currentUser?.link(with: pendingCred) { result, error in
// Link Facebook to Google Account
}
}
}
}
}
}
}
}
Users sign into my app with email authentication. Once inside they can browse and search for different things. But if the user wants to post they have to verify their phone number (if they don't want to post their phone number isn't necessary).
The phone number and sms process works fine but once I authenticate the PhoneAuthCredential the uid associated with the email that the user is currently signed in with is replaced with the uid generated from the phone credential. This creates a situation where an entirely new user is inside the app and because of this they don't have access to any of their data (anything associated with the uid from the email).
Basically the Auth.auth().currentUser?.uid was initially the email's uid and now the Auth.auth().currentUser?.uid would be the phone's uid
How can I verify the PhoneAuthCredential and keep the user currently signed in with their current email uid?
var emailUid: String? // a6UVVWWN4CeTCLwvkn...
var verificationId: String?
var phoneUid: String? // tUi502DnKlc19U14xSidP8
// 1. user signs into the app with their email address and their uid is a6UVVWWN4CeTCLwvkn...
Auth.auth().signIn(withEmail: emailTextField.text!, password: self.passwordTextField.text!, completion: {
(authDataResult: AuthDataResult?, error) in
self.emailUid = authDataResult?.user.uid // a6UVVWWN4CeTCLwvkn...
})
// 2. user goes to post something but before they can post they have to verify their phone number
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumberTextfield.text!, uiDelegate: nil) {
(verificationID, error) in
guard let verificationID = verificationID else { return }
self.verificationId = verificationID
}
// 3. sms code is sent to user's phone and they enter it
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationId!, verificationCode: smsTextField.text!)
// 4. now VERIFY sms code by signing the user in with the PhoneAuthCredential
Auth.auth().signInAndRetrieveData(with: credential, completion: {
(authDataResult, error) in
self.phoneUid = authDataResult?.user.uid // tUi502DnKlc19U14xSidP8 this is now the current user's uid
// 5. save phoneNumber and verificationId to the user's uid ref associated with the EMAIL address
var dict = [String: Any]()
dict.updateValue(verificationId!, forKey: "verificationId")
dict.updateValue(phoneNumberTextfield.text!, forKey: "phoneNumber")
dict.updateValue(self.phoneUid!, forKey: "phoneUid")
if Auth.auth().currentUser!.uid == self.emailUid! {
// THIS WILL NEVER RUN
let emailUidRef = Database.database().reference().child("users").child(emailUid!)
emailUidRef?.updateChildValues(dict)
}
})
You can link the two accounts together using Firebase Authentication's account linking. As that documentation says:
Complete the sign-in flow for the new authentication provider up to, but not including, calling one of the FirebaseAuth.signInWith methods.
So you skip signInAndRetrieveData(with: credential), but instead call User.linkAndRetrieveData(with: credential). Once the accounts are linked, you can sign in with either of them to get the "combined" authenticated user.
I am trying to link Facebook and Google. So, the scenario is this:
I have already authenticated with Google. So, now I am logging in Facebook, having same email id which was used earlier with Google. So, I get the error of account Exists with a different credential. And, I did this:
func fetchUserInfo()
{
Auth.auth().signInAndRetrieveData(with:FacebookAuthProvider.credential(withAccessToken: (FBSDKAccessToken.current().tokenString)!), completion: { (result, error) in
if let error = AuthErrorCode.init(rawValue: error!._code)
{
switch error
{
case .accountExistsWithDifferentCredential :
let credential = FacebookAuthProvider.credential(withAccessToken: (FBSDKAccessToken.current()?.tokenString)!)
Auth.auth().currentUser?.linkAndRetrieveData(with: credential, completion: { (result, error) in
if let error = error
{
print("Unable to link Facebook Account", error.localizedDescription)
}
else
{
NavigationHelper.shared.moveToHome(fromVC: self)
}
})
default: break
}
}
else
{
GeneralHelper.shared.keepLoggedIn()
if let currentUser = Auth.auth().currentUser
{
print(currentUser.email!)
}
NavigationHelper.shared.moveToHome(fromVC: self)
}
})
}
Here Firebase Documentation says that we need to just link the currentUser and retrieve data. But, the issue I am facing is that the currentUser is always nil. So, how can I get the current user? I have already tried this months ago and then I was able to link Facebook, Google and Email. Do, I need to signInAndRetrieve the data from Google in order to get the currentUser?
The Error "account Exists with a different credential" is because, by default, Firebase do not allow to use the same email address for two (or more) different Sing In methods. You need to enable this option.
1 - Go to Authentication > Sign-in method
2 - Scroll down to Advanced: Multiple accounts per email address
3 - Change the option to Allow creation of multiple accounts with the same email address
FYI: You need to do whole login process for each Sign In method in your app. Each method has is own credentials.
Hope this helps.
I am currently authenticating users using firebase phone Authentication and it works fine, however when I close the app and open it again I am redirected to the Authentication form and I receive an authentication Code each time. I don't want that kind of behavior. Is there is a way to check if the current user or the phone number is already authenticated without saving the user to a database
You can do it like checking for the currentUser in firebase, if the currentUser's phone number is same as the provided phone number in textField, then you can bypass the authentication and take user to home screen. Here is how it can be done.
if Auth.auth().currentUser != nil {
// USER IS SIGNED IN, BUT STILL WE HAVE TO CHECK, IF THE SAME USER IS SIGNING IN OR DIFFERENT.
if let user = Auth.auth().currentUser {
let phone = user.phone
if phone == YOUR TEXTFIELD VALUE OF PHONE {
// YOU HAVE THE LOGGED IN USER NOW, YOU CAN TAKE USER TO HOME SCREEN
} else {
// SIGNOUT THE CURRENT USER AND DO THE AUTHENTICATION FOR NEW USER
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
// No USER IS SIGNED IN, SO GET CREDENTIAL FROM YOUR SERVER AND SEND IT TO AUTH
Auth.auth().signIn(withCustomToken: customToken ?? "") { (user, error) in
// YOU HAVE THE LOGGED IN USER NOW, YOU CAN TAKE USER TO HOME SCREEN
}
}
}
} else {
// No USER IS SIGNED IN, SO GET CREDENTIAL FROM YOUR SERVER AND SEND IT TO AUTH
Auth.auth().signIn(withCustomToken: customToken ?? "") { (user, error) in
// YOU HAVE THE LOGGED IN USER NOW, YOU CAN TAKE USER TO HOME SCREEN
}
}
Check the procedure, if this is what you wants to achieve.
I have implemented firebase phone auth to verify phone number in my project and its working fine for me, But not able to update phone number. Like if a user had logged in with phone number A and now he wants to update this to phone number B. How will it be solved?
I have found an answer where you log in with your email account and update the mobile number into the same account. You may use the solution to log in with phone and update the phone number into the same account and see if it works. Anyway I will be working on this exact solution down in my project and update the answer then. But until then you can try to see if this works. Follow the regular firebase phone auth procedure as given here : https://firebase.google.com/docs/auth/ios/phone-auth
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
if let error = error {
self.showMessagePrompt(error.localizedDescription)
return
}
// Sign in using the verificationID and the code sent to the user
// ...
}
let credential = PhoneAuthProvider.provider().credential(
withVerificationID: verificationID,
verificationCode: verificationCode)
Then do not use the following code
// Sign In The User
Auth.auth().signInAndRetrieveData(with: credential) { _, error in
}
But use this code
Auth.auth().currentUser?.linkAndRetrieveData(with: credential, completion: { _, error in
if error == nil {
print("Whopdee doo")
} else {
print("Aargh!!!")
}
})