Changing email for iOS Firebase user authenticated with email link - ios

So I have an iOS app that uses Firebase, signs users in anonymously initially and then allows them to later add an email address, which is linked to the Firebase user using a call to:
Auth.auth().currentUser?.linkAndRetrieveData(with: credential)
This all works fine but I'm now running into the issue of being unable to update that user's email. I assumed this would work by simply sending another email link to the new email, then calling linkAndRetrieveData with the new credentials.
This, however, gives me a FIRAuthErrorCodeProviderAlreadyLinked error, because the user already has an email address. I would've expected it to just overwrite the existing email. I then tried/considered to solutions:
Unlink the email provider, then relink with the new credentials. First, this seems impossible since the stated "providerId" for an email-link-authenticated user is "Firebase", and when I feed that into unlink it fails. Second, this is bug prone because you could successfully unlink the first email and then have the link of the new email fail.
Use the provided "updateEmail" method - this doesn't work because it assumes email/password authentication, and I'm using email link authentication. Even if this did work, it would mean having an unverified email.
So I'm wondering how to do this, or if it's actually possible that this isn't supported at all. I can't believe this wouldn't be supported since it seems like a very common issue and basically makes email link authentication a nonstarter.

updateEmail() is the method to change email for signed in user, no matter how the user was created or authenticated. You are right that the new email address would become unverified. The sendEmailVerification() method can then be triggered to send a verification email to the user, so they can verify the email address.

The providerID for an email link user should be "emailLink" (Android link, since I couldn't find the iOS version quickly, and the values come from the server anyway).
But note that a linked account will always have a top-level provider of "firebase", and then have the actual values of the linked accounts in the UserInfo elements of the provider array.

Related

is unique email address from Apple when performing 'Sign in using Apple' always change?

I am trying to make login using OAuth2 using Apple Sign in. and I need to save the user email to the server.
after reading Sign in with Apple and authenticate with Firebase documentation , it said that
Unlike other providers supported by Firebase Auth, Apple does not
provide a photo URL.
Also, when the user chooses not to share their email with the app,
Apple provisions a unique email address for that user (of the form
xyz#privaterelay.appleid.com), which it shares with your app. If you
configured the private email relay service, Apple forwards emails sent
to the anonymized address to the user's real email address.
apple will generate unique fake email like xyz#privaterelay.appleid.com if the user refuses to share their original email.
if the user logout and then login again, will the fake email will be the same? or apple will generate a new fake email like new123#privaterelay.appleid.com ?

Firebase Auth link provider Google sign in issue?

At the first time, while signup with Gmail and password, firebase saved the credentials correctly. But the next time, I Login with Firebase Google authentication with the same Gmail which i gave while signup, the credentials are overriding in firebase account. After overriding the credentials, we are not able to login using that signup credentials. Can anyone explain how to achieve this?
What happened
In the first screenshot you signed in with the email+password provider of Firebase. While this is a valid sign-in method, it means that anyone could've entered that email address, even if they don't actually have access to the Google account for that gmail address.
There is no security risk here, but the level of trust we can put in the value of email address is low. For this reason the emailVerified property of the account is marked as false and you'll typically want to require that the user verify their email address before allowing them to continue.
In the second screenshot, the user signed in with the same email address, but now with the google.com provider of Firebase. This means that Google now verified already that the user has access to the underlying gmail address of the account. Since the google.com provider is the trusted provider for #gmail.com accounts, the system replaces the previous account.
Also see:
Authentication using Facebook at first and then Google causes an error in Firebase for Android
Firebase Overwrites Signin with Google Account
Trying to understand Firebase Authentication one account per email address and trusted providers
What you can do
You'll typically want to prevent multiple users from signing up with the same email address. For this, you'll want to configure Firebase to only allow a single account per email address in the console, and then use account linking so that the two (email+password and google.com) accounts in your scenario are merged.
Did you verify the email or phone number from the first login attempt? If not, this is by design:
After sign-in completion, any previous unverified mechanism of sign-in will be removed from the user and any existing sessions will be invalidated. For example, if someone previously created an unverified account with the same email and password, the user’s password will be removed to prevent the impersonator who claimed ownership and created that unverified account from signing in again with the unverified email and password.
Source
I just ran into this problem and here is a longer and more in depth description. (Things change often, this was true in Nov 2021.)
SHORT VERSION: As #Frank van Puffelen said, this is by design. The issue is that email+password is not a trusted provider usually, so a trusted provider like Google Authentication overwrites that method. It does this silently (I think, didn't check every field in GoogleSignInAuthentication object.)
It does auto-link after a password reset OR the email is verified via a link. See https://firebase.flutter.dev/docs/auth/usage/#verifying-a-users-email on code to do that.
Also: I don't recommend turning off One account per email address as some others suggests . See the reason for that at the end.
"Weird" Behavior under default One account per email address
In my app, the following happens.
SignUp via email+password for testUser1234#gmail.com.
creates an account for c_example_account#gmail.com with provider=Email/Password as indicated by the envelope/mail icon in the firebaseAuth dashboard.
LogOut and re-signin via Google Sign In for c_example_account#gmail.com
The provider is changed. Old provider is Email/Password icon (envelope). New provider is Google icon. (like the bottom three accounts in the screenshot). Note also that the User UID is the same. So anything anything linked to that User UID is still okay.
Since the Email/Password login method (AKA) provider was removed for c_example_account#gmail.com, the user can't login with that method anymore. IMPORTANTLY: This is done silently without the user getting any notification that the Email/Password login was removed.
Trying to sign on using Email/Password will result in an error Incorrect Password. Note: one might expect it to give an error like "Only Google Sign-In is available", but it doesn't. Contrast this to when the email doesn't exist (like trying garbage#123457.com), which has an error Email is not found...
Now, it gets a little weirder...
Suppose the user uses "Reset Password" like being called like this
Future<void> resetPassword(String email) async {
await _firebaseAuth.sendPasswordResetEmail(email: email);
}
Then, the firebaseAuth console has TWO methods for the same USER UID. See the second and third line in the screenshot.
Now, both methods are allowed. The difference is that the first time was a createUserWithEmailAndPassword() like
await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
...but this time it was created via a "Reset" event
Future<void> resetPassword(String email) async {
await _firebaseAuth.sendPasswordResetEmail(email: email);
}
... that gave a link via email sent by firebaseAuth service. In this case, the email was verified.
Recap: Now both methods work. The two methods being (1) Google authentication and (2) Email/Password. In Google parlance, the accounts have been linked: https://firebase.google.com/docs/auth/android/account-linking. Linking means One User UID, multiple login methods
Why the funky behavior when Email/Password is created in two different methods?
~~I couldn't find this documented in firebaseAuth, maybe because I didn't look hard enough or maybe because it's not a common issue. ~~
UPDATE: This behavior is documented in an issue comment from April 2020.
I think the reason is because the _firebaseAuth.createUserWithEmailAndPassword version has an unverified email. So, anyone can create an account for anyone else assuming that the email+password combination doesn't exist. For example, I could create an account with username president#whitehouse.gov without actually having access to that email. If the actual president logged in via Google Authentication, then I'd have bogus access to that user's info. Except that the clever google engineers decided that the verified Google Authentication then triggers the deletion of the unverified Email/Password provider/account instance.
In short, the logic might be: verified trumps/overrides unverified. See https://firebase.google.com/docs/auth/users#verified_email_addresses
Again, none of this is documented explicitly for Email/Password. But it is hinted at in the documentation, like if a Facebook Auth account gets over-written by a Google Auth.
Snapshot of the Verified Email details
Copied from: https://firebase.google.com/docs/auth/users#verified_email_addresses
Bolded added by me, for emphasis
In some situations, Firebase will automatically link accounts when a
user signs in with different providers using the same email address.
This can only happen when specific criteria are met, however. To
understand why, consider the following situation: a user signs in
using Google with a #gmail.com account and a malicious actor creates
an account using the same #gmail.com address, but signing in via
Facebook. If these two accounts were automatically linked, the
malicious actor would gain access to the user's account.
The following cases describe when we automatically link accounts and
when we throw an error requiring user or developer action:
User signs in with an untrusted provider, then signs in with another untrusted provider with the same email (for example, Facebook followed
by GitHub). This throws an error requiring account linking.
User signs in with a trusted provider, then signs in with untrusted provider with the same email (for example, Google followed by
Facebook). This throws an error requiring account linking.
User signs in with an untrusted provider, then signs in with a trusted provider with the same email (for example, Facebook followed
by Google). The trusted provider overwrites the untrusted provider.
If the user attempts to sign in again with Facebook, it will cause an
error requiring account linking.
User signs in with a trusted provider, then signs in with a different trusted provider with the same email (for example, Apple
followed by Google). Both providers will be linked without errors.
You can manually set an email as verified by using the Admin SDK, but
we recommend only doing this if you know the user really does own the
email.
Why not turn off One account per email address
By default, the setting One account per email address is active as #Deva wrote. But, unchecking this means that there are two different accounts (User UIDs) for the same email. One via Email/Password and one via Google Authentication. They will have separate User UIDs in Firebase Auth, so that may confuse you. Furthermore, if you manually link in your app two User UIDs, this creates a security hole: Someone can create an account without email verification to get access to an existing account. So don't do that.
Related StackOverflow questions and links
https://stackoverflow.com/a/60276351/233382
why i can't link email/password to the same email exist in google sign in provider in firebase flutter?
https://github.com/firebase/firebase-ios-sdk/issues/5344#issuecomment-618518918

How can users sign up to a Cognio User Pool through Facebook when email attribute is required but Facebook doesn't provide it?

My AWS Cognito Pool has email as a required attribute. All users who sign up to my app have been required to provide their email.
I've recently added Facebook as an Identity Provider to my user pool.
The problem is that Facebook doesn't always provide an email, and in those cases, Cognito will redirect new users to an error page saying "Email is Required".
I want to prompt the user to enter his/her email and then continue the sign up process, rather than just ending it with an error. Is there any way to do this?
This simply is not currently possible with Cognito User Pools.
I've hit the same example when working with clients, even when consenting to the appropriate scopes, Facebook will not provide it in the OpenID token.
I suspect though that the email is retrievable via the API following this, so you may have some luck with a Cognito User Pools trigger to go retrieve the email and stuff it in post-authentication if it's missing.
Otherwise there's not a lot you can do other than making email address optional and then designing customer experience around this.
If you have set 'Email' in the Facebook token scope, Facebook will provide the email address if it has one. However, Facebook itself doesn't always have an email address for a user
https://developers.facebook.com/docs/facebook-login/permissions/#reference-email
Note, even if you request the email permission it is not guaranteed
you will get an email address. For example, if someone signed up for
Facebook with a phone number instead of an email address, the email
field may be empty.
In this case I think you either have to live with the current Cognito behaviour, or make email optional, but effectively enforce it yourself with a Cognito Post-Authentication Lambda trigger

How to correctly link different Auth accounts in Firebase IOS

Background:
I am developing an IOS app using firebase as backend.
There are 3 authentication:
1:password and email
2:FaceBook
3:Google
I have checked the option "one email per account" option.
The situation is:
Say if I first sign in with one of the Auth provider and later, log out, and want to sign up with any other two Auth providers. I will get an "the email address has been used" error if the associated Email of the current provider is the same as previous. In this case I want to link the current Auth account with the previous account.
I understand that I need to call the linkWithCredential:completion: method to link the accounts. But I first need to sign In the previous account but how can I tell which account to sign in? For example, if I log in via Facebook and get the "same email being used" error, how do I know at this point whether should I sign in via Google or the email/password?
One interesting thing is If I use Facebook or email/password to sign in first and later sign in with Google, firebase will automatically handle the linking but the default behaviour is to overwrite the previous Auth provider with Google and keep the UID...
I have found an useful post How to manage users' different authentication in firebase
But it only deal with a simpler situation where authentication are only two.
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

Linking new users signed in via Facebook connect to existing accounts

I have recently implemented login to my via facebook connect. So now users have 2 ways of logging in to the site. The old way of registering an account and the new way (facebook connect).
One thing I would like to do is link a new facebook connect user account to existing accounts if they logged in the old way.
Has anyone had any success doing this?
Very good question I think and lots of people will benefit from an answer.
What you need to remember is that accounts are only linked so long as they are authorised to be linked through Facebook. What you should do is maintain a second table of linked accounts in your database so that you know who is who and if they are linked with Facebook.
You should read this integration comment, it provides a lot of useful information.
http://crazyviraj.blogspot.com/2010/01/test-cases-for-basic-facebook-connect.html
It doesn't really say how to do things, but it makes sure you tick all the boxes of what you should be doing.
ie:
Sign Up should fail if the user denies
permission to the app (category: sign
up)
Since we need access to an email
address, Sign Up should fail if the
user provides publish permission but
denies email permission (category:
sign up)
If the user provides an email address
that already exists in your system,
fail Sign Up. Make sure no YouFace
backend tables are modified (category:
sign up, 1:1 mapping) PS - when this
happens, I didn't find a way for you
to de-authorize YouFace on the
Facebook user's behalf. The user must
manually do this if they wish you use
the same account but provide a
different email address.
Accounts created using Facebook
Connect should not be able to login
using YouFace's default email/password
login system (category: sign in,
account security). PS: Since YouFace
accounts require a password and those
created using Facebook Connect don't,
make sure to insert a random password
hash into your table to avoid silly
errors
Accounts created using YouFace should
be able to sign in without requiring
to be signed into Facebook, even if
when a link to a Facebook accounts
exists (category: sign in)
Any many more
You should be asking for permanent access through fb connect authentication. Once you've done that, you'll get a token which gives your permission to access someone's Facebook information, and that token will not expire unless the user explicitly removes you from the permission list or changes his/her password.
Once you have the token, associate that token with the user / create a new field in your user table to store it.
To associate the user with a Facebook account without the user logging in, you can try to match by email. It's not 100% accurate but it's pretty good. Facebook doesn't give you email addresses in text form but you can get email hashes from FQL. Since you already know user email addresses, you can calculate the hash for all of your user emails and search through your user base for matches every time a new Facebook Connect user signs up.

Resources