Firebase Allowing Multiple Accounts from Same Email Address - firebase

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();
});
});
});
}
});

Related

Firebase auth overriding user providers (email + password, phone) when signing-in with Google

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.

Firebase email/password authentication - how to require email verification?

Whenever I use the email/password authentication provider in Firebase, the provider sends a bearer token upon successful sign-up even though the emailVerified is false. Is there a way, out of the box, to configure the email/password auth provider to not send a bearer token (and return a 403 error) until the user has verified their email address?
Note that I'm aware of how to create a user, sign in a user, send a verification email, etc... using firebase v9.x via the methods createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, sendEmailVerification from firebase/auth. I'm just asking if there is a way to set the behavior of the provider without having to write my own handler function for this. I'd like this to behave like Cognito does whenever the email verification is required.
There is no way to require the user's email address to be verified before they can sign in to Firebase Authentication.
The closest you can get is by using email-link sign-in, which combines signing in and verifying the user's email address in one action.
But this is how you'll typically want to implement this in your application code:
User enters their credentials
You sign them in to Firebase with those credentials
You check whether their email address is verified
If not, you stop them from further using the app - and (optionally) send them a verification email.
Same with data access: if you have a custom backend code, you can check whether the email address is verified in the ID token there too, as well as in Firebase's server-side security rules.
As per the documentation, you can use blocking functions to require email verification for registration (only that it doesn't work):
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
const locale = context.locale;
if (user.email && !user.emailVerified) {
// Send custom email verification on sign-up.
return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
return sendCustomVerificationEmail(user.email, link, locale);
});
}
});
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (user.email && !user.emailVerified) {
throw new functions.auth.HttpsError(
'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
}
});
generateEmailVerificationLink always returns the following error:
"err": {
"message": "There is no user record corresponding to the provided identifier.",
"code": "auth/user-not-found"
},
but the user is created anyway given that beforeCreate don't return an exception.
If you want to check by yourself just log the error:
return admin.auth().generateEmailVerificationLink(user.email)
.then((link) => {
functions.logger.info("link", {user: user, context: context, link: link})
})
.catch((err) => {
functions.logger.info("error", {user: user, context: context, err: err});
});
The createUserWithEmailAndPassword() will sign in user right after the account is created. Also there isn't any way to prevent users from logging in even if their email is not verified but you can actually check if email is verified in security rules or using Admin SDK to prevent users with unverified email from accessing your resources. You can use this rule in Firestore:
allow read, write: if request.auth.token.email_verified == true;
One workaround would be creating users using a Cloud function and Admin SDK which won't sign in users but do note that users can sign in.
If you want to prevent login unless the email is verified strictly, then you can disable account right after it is created. Now you may not be able to use sendEmailVerification() which requires user to be signed in at first place, you can always create your own solution for verifying email. The process might look something like:
Create a user and disable the account in a Cloud function
Generate some token or identifier for verifying email and send an email to user from same cloud function
Once the user visits that link and verifies the email you can enable it
Additionally, users can still create accounts by using REST API but you can disable sign ups so users can be created via Cloud function only which disables the user immediately.

Firebase API-created user can't sign in with email link with disableSignUp status true

I need to create users server-side using Firebase Admin.
I disallow signups using Firebaseui with disableSignUp status set to true.
I wish to use email link sign in only.
When I create the user (in Python) using
auth.create_user(email=email, display_name=name, photo_url=profile_image_url)
a user is created but the user has no providerData and is shown the message:
newuser#example.com is not authorized to view the requested page.
That's unexpected - the Firebaseui documentation says that users can be created through the API and one would expect such users to be able to log in!
When I create the user as above but with a password, they are allowed to log in but they are shown a password prompt.
How can I create a new email user and force email link sign in?
I already have a cloud function to notify users that their account has been created. I added
var actionCodeSettings = {
url: 'https://example.com/welcome',
handleCodeInApp: true,
};
const link = await admin.auth().generateSignInWithEmailLink(user.email, actionCodeSettings);
to the function, and they now get a link to complete their account setup. Landing on '/welcome' opens the firebaseui-auth-container where the message "Confirm your email to complete sign in" is shown. The email is confirmed, and the user is changed to an emailLink user. It's a quite natural flow, almost as if it was designed for that purpose...

With IdentityServer4 and Twitter sign in is there a way to connect an existing account?

With .Net Core 3.1 and IdentityServer4, I have successfully set up Twitter sign in.
However, if I already created an account with that same email address (independently of Twitter)... when I click login in with Twitter, it then redirects me back to the identity server External Login page with the following message:
You've successfully authenticated with Twitter. Please enter an email address for this site below and click the Register button to finish logging in.
and a textbox with my twitter email address already filled in: [ myemail#mydomain.com ]
When I click Register I get the error message:
User name 'myemail#mydomain.com' is already taken.
This makes some sense... but it would be really nice if I had the option of connecting the Twitter login to the existing account... Is there any way to do this?
Its up to you in the ExternalController.Callback method in IdentityServer to handle the mapping to existing accounts and to create new accounts for new users.
For example, see this code:
// lookup our user and external provider info
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
if (user == null)
{
// this might be where you might initiate a custom workflow for user registration
// in this sample we don't show how that would be done, as our sample implementation
// simply auto-provisions new external user
user = AutoProvisionUser(provider, providerUserId, claims);
}

How to get firebase authentification linking account working with Unity?

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

Resources