I want to provide users multiple authentification providers with phone, facebook and a simple email and password.
the thing is, i want the same account to be provided (same user id, same display name) if the account is linked with the same Email address.
as i got to understand it through the documentation, you can do it if the user connects and link an account when already connected with another provider.
so my question is : is there anyway to do it automatically, when let's say, the user connects with another provider next time, without having to tell him to link it himself.
[Edit - after further investigation, I found a better answer]
It turns out that only allowing the email address once comes with some caveats - that are really well laid out in this GitHub issue but I'll try to summarize.
You can have an unverified account, which may allow you to register over it (the user hasn't proved that they own the account).
Next are kind of the social identity providers. Facebook is assumed to have verified your email once (for example), so you may get a duplicate account exception.
Finally, there are email identity providers - these are seen as authoritative for all email addresses that they're associated with and will overwrite any account with the same address for the domains that they're authoritative over. Both Google and Apple sign ins have this control over their respective domains for example.
See this section of the web documentation and this and this stack overflow answers.
At this point, you may be wondering how to get more control over the process if this isn't the user flow you want.
You may opt to make a call to FirebaseAuth.DefaultInstance.FetchProvidersForEmailAsync before allowing the user to log in. Then you can choose to prompt them to log back in with their existing account first to link accounts before creating a new one (and potentially replacing the old account). For example:
public async void LogIn(string email, Credential credential) {
var auth = FirebaseAuth.DefaultInstance;
var identityProviders = await auth.FetchProvidersForEmail(email);
if (identityProviders.Empty()) {
var user = auth.SignInWithCredentialAsync(credential);
}
else {
// somewhere in here, log in the user and call
// user.LinkWithCredentialAsync(credential)
DisplayLinkAccountDialog(identityProviders, email, credential);
}
}
[Old answer - information still applicable but incomplete]
By default, Firebase authentication only allows one email address per user. See this section at the bottom of your Authentication Providers page in the Firebase console:
So when you sign up a new user, you should get a FirebaseException with the error code AccountExistsWithDifferentCredentials.
To handle this, you'd make your continuation look something like:
/* Registration call */.ContinueWithOnMainThread(task=>{
if (task.Exception) {
foreach (var e in task.Exception.InnerExceptions) {
var fe = e as FirebaseException;
if (fe != null) {
if (fe.ErrorCode == AuthError.AccountExistsWithDifferentCredentials) {
TryToLinkInsteadOfCreateAccount(); // TODO - cache credentials and implement this
}
}
}
}
});
Let me know if that helps!
--Patrick
Related
As we have seen in another posts related to this situation and github issues, the expected behavior is that Google IDP overrides other non trusted providers related to the same email as an example, another account with the same email + password (non-verified).
Trying to understand Firebase Authentication one account per email address and trusted providers
Firebase Overwrites Signin with Google Account
https://github.com/firebase/firebase-ios-sdk/issues/5344
https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ
So, ok, according to google that's the expected behavior.
Our questions comes when we go to the documentation and there's an example of a user login in with google and getting this error auth/account-exists-with-different-credential just because there's another account created with email+password with the same email. Then, they recommend to catch the error, check the user email related login methods and ask the user to login with the other provider and then link to google.
Does this make sense ? If they say the expected behavior is that google as a trusted provider will override the others (this is what happens to us) how is possible that the case of the code example would even occur ?
https://firebase.google.com/docs/auth/web/google-signin#expandable-1
// Step 1.
// User tries to sign in to Google.
auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).catch(function(error) {
// An error happened.
if (error.code === 'auth/account-exists-with-different-credential') {
// Step 2.
// User's email already exists.
// The pending Google credential.
var pendingCred = error.credential;
// The provider account's email address.
var email = error.email;
// Get sign-in methods for this email.
auth.fetchSignInMethodsForEmail(email).then(function(methods) {
// Step 3.
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (methods[0] === 'password') {
// Asks the user their password.
// In real scenario, you should handle this asynchronously.
var password = promptUserForPassword(); // TODO: implement promptUserForPassword.
auth.signInWithEmailAndPassword(email, password).then(function(result) {
// Step 4a.
return result.user.linkWithCredential(pendingCred);
}).then(function() {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
return;
}
// All the other cases are external providers.
// Construct provider object for that provider.
// TODO: implement getProviderForProviderId.
var provider = getProviderForProviderId(methods[0]);
// At this point, you should let the user know that they already have an account
// but with a different provider, and let them validate the fact they want to
// sign in with this provider.
// Sign in to provider. Note: browsers usually block popup triggered asynchronously,
// so in real scenario you should ask the user to click on a "continue" button
// that will trigger the signInWithPopup.
auth.signInWithPopup(provider).then(function(result) {
// Remember that the user may have signed in with an account that has a different email
// address than the first one. This can happen as Firebase doesn't control the provider's
// sign in flow and the user is free to login using whichever account they own.
// Step 4b.
// Link to Google credential.
// As we have access to the pending credential, we can directly call the link method.
result.user.linkAndRetrieveDataWithCredential(pendingCred).then(function(usercred) {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
});
});
}
});
There's another example with the same structure in the flutter docs:
https://firebase.google.com/docs/auth/flutter/errors#handling_account-exists-with-different-credential_errors
Is this a contradiction in the documentation ? Again, if Firebase will always give priority to the trusted IDP (Google email) in this case, how is it possible to get this error if the other provider will be deleted (at least when having account linking activated - single account per email activated)
At least this is our case. We create an account with email & password and then try to login with google with the same email and what happens is that the email&password account is overwritten by the new google provider.
Unfortunately, you can't change it. If a user with #gmail.com email and password authentication updates their profile picture and then later logins with Google then the profile picture and any other information will be overwritten with the data from Google. The only option is to create a user record in the database that gets populated with the user data (displayName, photoURL etc) when the user is created for the first time. You then always use the data from this record instead of the default user object that is returned by the authentication.
The other advantage of creating a record is that you can attach a listener to it. That way if the user changes their details then it gets reflected everywhere.
My app has gmail and facebook authentication integrated through Firebase. I noticed if someone signs up with their gmail then signs up with Facebook, if the Facebook had the same email as their gmail then they'll get the error:
"The email address is already in use by another account."
Is the only reasonable way to handle this to tell the user to sign in with different credentials? Maybe show a message like "Email already in use, please sign up with different account"?
There are 3 ways in which you can handle this problem.
The first one is to verify if the email address exists and then display a message. This is exactly what you said. The message is up to you.
The second approach is to enable users to have multiple accounts per email address. In other words, if a user signs up with Gmail and then signs up with Facebook and he has the same email address then he ends up having 2 different accounts. A single email address, 2 different accounts This is not a good practice but according to your needs, you can even use it.
The third approach is to have only one account per email address. This means that you are preventing the users from creating multiple accounts using the same email address with different authentication providers. This is a common practice and also the default rule in the Firebase console. This means, that you'll want to implement later another kind of authentication with another provider, and it will follow the same rule. In this case, will have a single email address with a single account.
To enable or disable this option, go to your Firebase console, choose Authentication, select the SIGN-IN METHOD tab, and at the bottom of your page you'll find the Advanced section.
It happened to me and it was because I was calling the method wrong.
use signInWithEmailAndPassword
Since there are a lot of similar questions to this topic I am posting my method for this error since I did not find an answer that suits a specific situation.
Lets say you have enabled an E-Mail login and also a Facebook login and a user registers via email login first and then tries to login with Facebook with the same email. Now, if you don't want to link this account with the existing one, or don't want to enable multiple accounts with one email, you can just add a Toast message that will notify the user that the email is already in use. And he cannot login via Facebook then. Here is my approach that handles the error when a user tries to register via Facebook with an already existing email:
private void handleFacebookAccessToken(AccessToken token) {
Log.d(TAG, "handleFacebookAccessToken:" + token);
AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());
mAuth.signInWithCredential(credential)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()){
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success");
FirebaseUser user = mAuth.getCurrentUser();
getTokenId();
updateUI(user);
} else {
Toast.makeText(SignInActivity.this, "Your Facebook email is already in use.", Toast.LENGTH_LONG).show();
}
}
});
}
I basically used the code provided by Firebase and added and if-else statement (if (task.isSuccessful())
Hope it will help someone!
void createuser(String email, String password) async {
try{
await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
}
on FirebaseAuthException catch (e){
if(e.code == "email-already-in-use"){
Get.showSnackbar(const GetSnackBar(
margin: EdgeInsets.all(15),
borderRadius: 8,
message:
('There already exists an account with the given email address.'),
duration: Duration(seconds: 3),
backgroundColor: Colors.red,
));
}
}
}
Here is an example for the 'email-already-in-use' firebase authentication error.
Within the Firebase console I have specifically set it to only allow "One account per email address". This is found on the sign-in method tab under "advanced".
I have an account created using the Google login method that has an address like "me#gmail.com". If I then choose to sign-in via Facebook using an account that also uses "me#gmail.com", Firebase is allowing it with the exception that the email address in the Users entity is null.
The Firebase documentation states:
if you don't allow multiple accounts with the same email address, a
user cannot create a new account that signs in using a Google Account
with the email address ex#gmail.com if there already is an account
that signs in using the email address ex#gmail.com and a password.
Does this only count if you are trying to create a Firebase login directly with a username/password vs creating an account from two providers like Facebook and Google? I would be under the impression that if it finds a duplicate email address it should reject the registration/login. I do realize the quote states "and a password" which makes me wonder.
Go to Firebase Console
In the Authentication -> SIGN-IN METHOD
Scroll Down to Advanced Section
Click on CHANGE and then SAVE
Step 1 : Go to Firebase Console > Authentication > Sign in method. Check the option preventing multiple account creation with single email id.
Step 2 :The following documentation explains how to connect multiple providers to a single account using custom method.
https://firebase.google.com/docs/auth/web/account-linking
#AndroidBeginner's answer helped solve this for me, however, the "Account Email Settings" option is no longer under Authentication --> Signin Method, I found it under Authentication --> Settings, and it's the first option at the top "User Account Linking".
Clicking on "Create multiple accounts for each identity provider" did solve my error in chrome console, and I am able to login/register in my app with chrome and Facebook, but now I do have multiple users with the same email...
Expanding Kathir's answer, Firebase documentation does provide solution.
The following are code snippets copied from the documentation.
// Step 1.
// User tries to sign in to Google.
auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).catch(function(error) {
// An error happened.
if (error.code === 'auth/account-exists-with-different-credential') {
// Step 2.
// User's email already exists.
// The pending Google credential.
var pendingCred = error.credential;
// The provider account's email address.
var email = error.email;
// Get sign-in methods for this email.
auth.fetchSignInMethodsForEmail(email).then(function(methods) {
// Step 3.
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (methods[0] === 'password') {
// Asks the user their password.
// In real scenario, you should handle this asynchronously.
var password = promptUserForPassword(); // TODO: implement promptUserForPassword.
auth.signInWithEmailAndPassword(email, password).then(function(user) {
// Step 4a.
return user.linkWithCredential(pendingCred);
}).then(function() {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
return;
}
// All the other cases are external providers.
// Construct provider object for that provider.
// TODO: implement getProviderForProviderId.
var provider = getProviderForProviderId(methods[0]);
// At this point, you should let the user know that he already has an account
// but with a different provider, and let him validate the fact he wants to
// sign in with this provider.
// Sign in to provider. Note: browsers usually block popup triggered asynchronously,
// so in real scenario you should ask the user to click on a "continue" button
// that will trigger the signInWithPopup.
auth.signInWithPopup(provider).then(function(result) {
// Remember that the user may have signed in with an account that has a different email
// address than the first one. This can happen as Firebase doesn't control the provider's
// sign in flow and the user is free to login using whichever account he owns.
// Step 4b.
// Link to Google credential.
// As we have access to the pending credential, we can directly call the link method.
result.user.linkAndRetrieveDataWithCredential(pendingCred).then(function(usercred) {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
});
});
}
});
Heres my problem:
I wan't to be able to create new users for my website, from my website. This is only aloud though, if I have the "isAdmin" flag set to true in the realtime db under /users/myid.
Generally I would have done this with a security rule but the problem here is that I need to use the admin SDK, since the normal "createNewUser" method signs in to the newly created user automatically. I as an admin though, want to be able to create a new user and stay logged in as myself. So what I wan't to do is use ajax post request to my server with my uid und the new userdata which is to be created. My server then checks if the given uid has the isAdmin flag and if so creates the user with the firebase admin SDK which provides such a method.
But, anyone, if they have an admins uid, could hit up that request and create a new user. (My clients definetely get uid's from other users).
So how would I go about proving to the server that I am actually logged in with that uid.
From my understanding, tokens are used to be able to write to the database, but I don't need that permission, I just need to prove that I'm actually logged in with that uid.
Is there something I'm missing? Thanks a lot guys!
Was easier then I thought. This will generate a token on the client side:
firebase.auth().currentUser.getToken(true).then(function(token) {
// send request to server with generated token
}).catch(function(error) {
// handle error
});
Which I can then verify on the server like so:
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
var uid = decodedToken.uid;
// user is logged in
}).catch(function(error) {
// user is not logged in, or other error occured
});
Taken from https://firebase.google.com/docs/auth/admin/verify-id-tokens
As said in the title, no matter how I try the Facebook login, the emailVerified field is always false. Is this by design? I've read through the whole firebase docs by now, can't seem to find any information regarding this. Just to be sure: I've tried with 4 different verified accounts, the result is always the same. Any idea what could cause this kind of behavior?
the reason why Google provider emails are verified and Facebook emails are not is because Google is considered a trusted provider (You can create an email account using Google). Let's take another example. If you set up an email with yahoo, you will get an email myself#yahoo.com. If you sign in using yahoo OAuth 2.0, you know for sure that user is verified since Yahoo is the actual owner and issuer of that email address. However, you could also use that same email to create a facebook account or some other account like github or twitter and verify using your phone number or some other means. In that case, if you sign in using Facebook, the email is not verified (facebook does not own or manage that email address). Normally if you wish to verify the email in that case, you have to send the email verification (experimental at the moment and only available in web and iOS but should eventually come to android).
The solution I provide would probably be useless to the OP since it was asked last year but hope it helps someone else. While I agree with bojeil's answer, it's somewhat annoying for real users to verify their Facebook email address when signing in with Facebook.
I encountered this problem on Android today and applied a work around since isEmailVerified() If condition always threw false and returned the user back to login page, here's the work around extracted from my code:
FirebaseUser mUser = mAuth.getCurrentUser();
if(!mUser.getProviders().get(0).equals("facebook.com")) {
if (mUser.isEmailVerified()) {
Intent mainIntent = new Intent(getActivity(), MainActivity.class);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(mainIntent);
} else {
Snackbar.make(getView().findViewById(R.id.loginLayout), "Please verify your account!", Snackbar.LENGTH_LONG).show();
}
}else{
Intent mainIntent = new Intent(getActivity(), MainActivity.class);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(mainIntent);
}
The first If statement checks if the user is signing in with Facebook,if yes the user is taken to the MainActivity, if not the isEmailVerified() method is invoked normally for email/password users and for Google sign in usersisEmailVerified()always returns true.
Firebase provides a process for "verifying" an email address -- but NOT for all platforms yet. This feature is not available for Android ... in fact, one cannot even query whether an eMail has been verified using Android code (even if you used a web or server code to perform the verification).
The "expected" process would normally be:
Authenticate a user's email (using any of the providers)
Call the Firebase function to send an eMail for verification
Respond to a verification link by setting verified Check, using client, to see if the eMail has been verified (could be days for the
user to handle)
Until eMail is verified, disallow appropriate
functions in your code (e.g. linking different authenticated
providers)
If you use an Android client currently, you cannot instigatge step 2.