Firebase Authentication with Google issue - android

I have been using Firebase Authentication in my app, and have noticed an issue with a particular use case.
I have enabled account linking sign up flow for my app, and thus I can attach multiple providers associated with a single email address.
Scenario 1: (Works fine)
The user has signed up with Google initially and sometime later, signs in in with Facebook or registers with email and password.
The account linking works fine and Facebook and/or Email is added in the provider list.
So, I can have 2 or 3 providers for the email, Google (initially), Facebook and Password (after that).
Scenario 2: (The bug)
The user has signed up with Facebook and/or Email initially and later signs in with Google, now the account linking doesn't work. Google replaces the previous providers present.
Account linking fails, and I just have Google as the sole provider associated with the email address and the others are gone.
In the second scenario, while signing in with Google, it should fail and throw FirebaseAuthCollisionException but it doesn't and succeeds. This is the main issue.
I can't paste the whole code here, but just a snippet for sure.
firebaseAuth
.signInWithCredential(credential)
.addOnFailureListener(exception -> {
if (exception instanceof FirebaseAuthUserCollisionException) {
mCredentialToLinkWith = credential;
if (mProviderList.size() == 1) {
if (mProviderList.contains(EmailAuthProvider.PROVIDER_ID)) {
mRegisterProviderPresenter.linkWithEmailProvider(credential, email);
} else {
linkProviderAccounts(email, AuthenticationHelper.getProviderToLinkAccounts(mWeakActivity, mProviderList));
}
} else {
linkProviderAccounts(email, AuthenticationHelper.getProviderToLinkAccounts(mWeakActivity, mProviderList));
}
} else {
Timber.d("Failed in signInWithCredential and unexpected exception %s", exception.getLocalizedMessage());
mRegisterProviderPresenter.onRegistrationFailed(new ErrorBundle(ErrorBundle.FIREBASE_ERROR, exception.getLocalizedMessage()));
}
})
.addOnSuccessListener(authResult -> {
Timber.d("Success: signInCred");
FirebaseUser firebaseUser = authResult.getUser();
/**
* Store the user details only for first time registration
* and not while acc linking
*/
storeUserCredentials(firebaseUser);
AuthenticationHelper.logUserDetails(firebaseUser);
mRegisterProviderPresenter.onRegistrationSuccess(mAlreadyRegistered);
});
Hope someone can come up with some help.

Facebook is a social identity provider and it doesn't own the emails. If an email is hacked, Facebook can't detect it and disable the account registered by this email. While Google is an email provider, its accounts are considered to be more secure.
Based on this theory, scenario 2 is different from 1. In scenario 1, the user has proved the ownership of this email by signing with Google first. So the user is allowed to add Facebook account using the same email. In scenario 2, Facebook sign in happens first and this provider record is untrusted, so it's removed when user signs in with another trusted provider.
Your code behavior is correct in both scenarios.

I faced the same issue and this is a supplemental answer for the question in the comment i.e.
Why is that after initially registering with a email & password, and then with Google, Google still replaces it?
I did some more exploration and found the answer here.
Pasting the relevant snippet.
If there is an existing account with the same email address but created with non-trusted credentials (e.g. non-trusted provider or password), the previous credentials are removed for security reason. A phisher (who is not the email address owner) might create the initial account - removing the initial credential would prevent the phisher from accessing the account afterwards.
The solution to handle this, i.e. to prevent Google from replacing the existing provider with Google, is to verify the email of the user.
So, after the user creates the account with email & password, or logs in with Facebook (or any other provider), send an email verification link to the user.
After the user verifies his/her email, then the subsequent Sign-in with Google will NOT replace the existing providers.

just use the email and password auth for the moment or a 3rd party plugin no solution so far

Related

Link several user accounts (same email) Firebase

I have the possibility that the user can choose if they want to log in with Google, Facebook, email/password, etc.
After testing my app, the following happened:
I sign up with my name, email, and password
Handle the get started logic
Verify my auth users on Firebase (grey email icon)
Sign out of the account
Now, I want to log in with Google (same email used on the sign-up with email and password)
The Google sign-in worked
Verify my auth users on Firebase (the grey email icon changed into the Google one)
Sign out of the account
Can't log in with email and password anymore but the google sign in worked
After some research, I end up with the Link Multiple Auth Providers to an Account on Android documentation
I realized I have to refactor my code to not use the FirebaseAuth.signInWith methods
This is a little except of my loginEmailAndPassword:
val credential = EmailAuthProvider.getCredential(email, password)
firebaseAuth.currentUser!!.linkWithCredential(credential).addOnCompleteListener{ authTask: Task<AuthResult> ->
if (authTask.isSuccessful) {
I have an 'else' meaning the (authTask.isSuccessful) did not happened and another 'if' with the FirebaseAuthUserCollisionException
val exception: java.lang.Exception? = authTask.exception
if (exception is FirebaseAuthUserCollisionException) {
linkAndMerge(credential)
My goal is to link and merge, and I do not know how to link the accounts (both email grey and Google on Firebase)
private fun linkAndMerge(credential: AuthCredential) {
val authenticatedUserMutableLiveData: MutableLiveData<ResponseState<UserModel>> =
MutableLiveData()
val prevUser = firebaseAuth.currentUser
firebaseAuth.signInWithCredential(credential)
.addOnSuccessListener { result ->
val currentUser = result.user
// Merge prevUser and currentUser accounts and data
// ...
}
.addOnFailureListener {
authenticatedUserMutableLiveData.value = ResponseState.Error("Error")
}
}
My questions:
Can I call something to merge prevUser and currentUser accounts. I just want to the user have the possibility of using different authentications.
I am not worried about the data because if it's the same User UID does not matter if the authentication provider
Can I still use 'createUserWithEmailAndPassword'?
Steps 1 to 9 provide the expected behavior. If you create a user with email and password and right after that you sign in with Google, the account will only be accessible with Google. Why? Because behind the scenes Firebase converts the account that was created with email and password into an account with the Google provider. Unfortunately, you cannot reverse that change.
The link in your question, is referring to the possibility to link an existing account to a specific provider. For example, if you implement anonymous authentication, then you can link that account with Google, for example. This means that the UID remains the same.
If you want to stop that mechanism from happening, then you should consider allowing the creation of different accounts for different providers. You can find this option which is called "Create multiple accounts for each identity provider" right inside the Firebase Console, in the Settings tab inside the Authentication.

Use Smart Lock with Firebase Auth Email+Password accounts

I'm not using FirebaseUI. With FirebaseUI I managed to store the email and password into Smart Lock after creating a new account for an user of the app.
I had to remove FirebaseUI, because it doesn't allow a precise control over which account gets signed in with silent sign-in, and I rely on that to switch between the multiple accounts one user can have on a device.
Using the CredentialsClient I had no problems retrieving those passwords; the user got presented a Dialog where he could choose the account and the password would get handed over to the app via onActivityResult. I followed Google's guide Retrieve a user's stored credentials in order to do this.
But I am unable to save the new email and password as a new credential. I always get the error com.google.android.gms.common.api.CommonStatusCodes.SIGN_IN_REQUIRED with the message Passphrase required in form of an com.google.android.gms.common.api.ApiException.
This is my code:
CredentialsOptions options = new CredentialsOptions.Builder()
.forceEnableSaveDialog()
.build();
CredentialsClient mCredentialsClient = Credentials.getClient(MainActivity.activity, options);
Credential credential = new Credential.Builder("email#example.com")
.setPassword("dummy-password")
.build();
mCredentialsClient.save(credential).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
LogWrapper.i(TAG, "Credentials saved");
}
else if (task.getException() instanceof ResolvableApiException) {
ResolvableApiException rae = (ResolvableApiException) task.getException();
LogWrapper.e(TAG, "RAE EXCEPTION WHEN SAVING PASSWORD:", rae);
}
else {
Exception e = task.getException();
LogWrapper.e(TAG, "EXCEPTION WHEN SAVING PASSWORD:", e);
}
}
});
I have three Google Accounts on that device, one of which has configured a passphrase for syncing, the other two don't.
Keep your info private
With a passphrase, you can use Google's cloud to store and sync your
Chrome data without letting Google read it. Your payment methods and
addresses from Google Pay aren't encrypted by a passphrase.
Passphrases are optional. Your synced data is always protected by
encryption when it's in transit.
I am not sure if this error message is being caused because of this, that this is the passphrase which is required. But then again I wonder why FirebaseUI had no issues with storing the Credentials, even into the correct account without asking, which is one which doesn't use a passphrase.
I also signed into the account via FirebaseUI's Google Sign-In where I want to save the credentials to, and then executed the code above in order to see if that signed-in user would set the app into a state where it is signed into that Google Account as the error messages seems to expect me to, and willing to save some email and password credentials into that account. That didn't make any difference.
I read Firebase's approach to using Smart Lock, and I can't find any real differences to what I'm doing.
It is basically the same as shown in the CredentialsQuickstart app.
So the question is: What needs to be signed in? Why am I getting this message?
Update: I've downloaded and compiled https://github.com/android/identity-samples/tree/master/CredentialsQuickstart and there I'm getting the same error, so this seems to be some more general issue.
This is on Android 10. I've also tried it on another device with Android 9 and there I have the same problem.
Has this API been "deprecated" by this thing called "AutoFill service"? I found an option in "Languages & Input" under "Tools" where there is an AutoFill Service from Google where I can select one account into which the credentials get stored into. And this is configured to use the account which has no passphrase, the one where FirebaseUI correctly stored the Credentials into. I think on a Samsung device it also offers an additional AutoFill service.
That would be this then: https://developer.android.com/guide/topics/text/autofill

FirebaseAuth - Let user change password using any provider - Android

I have a social media app, I'm using FirebaseUI to let users sign in/up to the app,using Email, Google or Facebook.
How can I let user to change his/her password later if using "Email" as a
provider?
If using Facebook or Google as providers can I let him/her set Email-Password as Authentication Method by giving him/her an option to change password?.
The change password action from user should set Email-Password as Authentication Method in firebase with a new password input from the user.
Then, The user should be able to login using Email-Password or the Authentication Provider( Facebook/Google) linked with same email.
Answering your question:
Yes.
Here is a sample code snippet for changing/updating the user password:
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
String newPassword = "SOME-SECURE-PASSWORD";
user.updatePassword(newPassword)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "User password updated.");
}
}
});
Details of using Firebase User is available here.
2.
a. Changing the password in your app:
NO
The SignIn Providers such as Facebook, Google and Twitter do not open this features (API) to prevent middleman and other attacks/abuses.
b. User changed the password in the service provider after signed-in
The user is still able to login to your app, authentication process is deal directly to the service provider, so you don't have to worry!
c. Multiple authentication with the same email address.
Referring to
let's say user A logged in using Facebook to my app, then he went to his profile in MY APP , and changed his password, next time to login I want him to have 2 options: 1- Login using Facebook normally 2- Login using his facebook Email + the password that he saved earlier
The answer is YES, but you have to merge the details first, here is the reference. You can actually link/merge the user details of the same email address by identifying the same Firebase user ID regardless of the authentication provider they used to sign in.
For example, a user who signed in with a password can link a Google account and sign in with either method in the future. Or, an anonymous user can link a Facebook account and then, later, sign in with Facebook to continue using your app.
Hope it helps!

React Native Read User Email Without Prompting

If I install an app on Google Play I notice that it informs me about what permissions the app requires. Once it does it in the app it also prompts me. This is all very fine.
I would like to just read the Google or Apple account e-mail - without prompting the end user for an e-mail.
I don't want to use the email as an auth token, but prefill a "Subscribe to News Letter" field. The user can then toggle ON or OFF (or choose to add another email).
If I'm getting the question right,
You need to get the primary email address of the currently signed in user, which can be used to pre-fill some kind of a form (eg. sign-up form)
For Android
Try the following react native wrapper library react-native-account-manager
Once you have setup using the instructions on the readme from the above link, use the following code to retrieve the list of signed in google accounts.
Please note this could result in an empty list, if there are no google accounts associated with the device
import AccountManager from 'react-native-account-manager';
AccountManager.getAccountsByType('accountName').then((accounts) => {
// console.log('available accounts', accounts);
let [firstAccount] = accounts;
AccountManager.getUserData(firstAccount, 'storedKey').then((storedData) => {
// console.log('stored data for storeKey', storedData);
AccountManager.setUserData(account, 'storedKey', JSON.stringify({foo: "bar"})).then(() => {
// console.log('data successfully stored');
})
});
})
For iOS
Bad luck
Apple does NOT expose the iPhone user’s details like their email address, password or credit card details to the app developer.
An alternative is to use the icloud token to create the user. You could use the following guide to do it
For a wrapper on obtaining a iCloud token using react-native use react-native-icloud-user-tokenlibrary
Kudos

How to do a email verification to firebase user for signup and email update using FirebaseAuth? [duplicate]

Question says it all. In Firebase, how do I confirm email when a user creates an account, or, for that matter, do password reset via email.
I could ask more broadly: is there any way to send emails out from Firebase? E.g. notifications, etc. This isn't the kind of thing you would usually do client-side.
Update
Note that this was never a very secure way of handling email verification, and since Firebase now supports email verification, it should probably be used instead.
Original answer
I solved the email verification using the password reset feature.
On account creation I give the user a temporary (randomly generated) password. I then trigger a password reset which will send an email to the user with a link. The link will allow the user to set a new password.
To generate a random password you can use code similar to this:
function () {
var possibleChars = ['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?_-'];
var password = '';
for(var i = 0; i < 16; i += 1) {
password += possibleChars[Math.floor(Math.random() * possibleChars.length)];
}
return password;
}
Note that this is happening on the client, so a malicious user could tamper with your logic.
This would need to be done outside of firebase. I store users at /users/ and keep a status on them (PENDING, ACTIVE, DELETED). I have a small service that monitors users of a PENDING status and sends out a confirmation email. Which has a link to a webservice I've created to update the user status to ACTIVE.
[Engineer at Firebase - Update 2014-01-27]
Firebase Simple Login now supports password resets for email / password authentication.
Each of the Simple Login client libraries has been given a new method for generating password reset emails for the specified email address - sendPasswordResetEmail() on the Web and Android, and sendPasswordResetForEmail() on iOS.
This e-mail will contain a temporary token that the user may use to log into their account and update their credentials. This token will expire after 24 hours or when the user changes their password, whichever occurs first.
Also note that Firebase Simple Login enables full configuration of the email template as well as the sending address (including whitelabel email from your domain for paid accounts).
To get access to this feature, you'll need to update your client library to a version of v1.2.0 or greater. To grab the latest version, check out https://www.firebase.com/docs/downloads.html.
Also, check out https://www.firebase.com/docs/security/simple-login-email-password.html for the latest Firebase Simple Login - Web Client docs.
As at 2016 July, you might not have to use the reset link etc. Just use the sendEmailVerification() and applyActionCode functions:
In short, below is basically how you'll approach this, in AngularJS:
// thecontroller.js
$scope.sendVerifyEmail = function() {
console.log('Email sent, whaaaaam!');
currentAuth.sendEmailVerification();
}
// where currentAuth came from something like this:
// routerconfig
....
templateUrl: 'bla.html',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn() // this throws an AUTH_REQUIRED broadcast
}]
}
...
// intercept the broadcast like so if you want:
....
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
if (error === "AUTH_REQUIRED") {
$state.go('login', { toWhere: toState });
}
});
....
// So user receives the email. How do you process the `oobCode` that returns?
// You may do something like this:
// catch the url with its mode and oobCode
.state('emailVerify', {
url: '/verify-email?mode&oobCode',
templateUrl: 'auth/verify-email.html',
controller: 'emailVerifyController',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn()
}]
}
})
// Then digest like so where each term is what they sound like:
.controller('emailVerifyController', ['$scope', '$stateParams', 'currentAuth', 'DatabaseRef',
function($scope, $stateParams, currentAuth, DatabaseRef) {
console.log(currentAuth);
$scope.doVerify = function() {
firebase.auth()
.applyActionCode($stateParams.oobCode)
.then(function(data) {
// change emailVerified for logged in User
console.log('Verification happened');
})
.catch(function(error) {
$scope.error = error.message;
console.log(error.message, error.reason)
})
};
}
])
And ooh, with the above approach, I do not think there's any need keeping the verification of your user's email in your user data area. The applyActionCode changes the emailVerified to true from false.
Email verification is important when users sign in with the local account. However, for many social authentications, the incoming emailVerified will be true already.
Explained more in the article Email Verification with Firebase 3.0 SDK
What I did to work around this was use Zapier which has a built in API for firebase. It checks a location for added child elements. Then it takes the mail address and a verification url from the data of new nodes and sends them forwards. The url points back to my angular app, which sets the user email as verified.
As I host my app files in firebase, I don't need have to take care of any servers or processes doing polling in the background.
There is a delay, but as I don't block users before verifying mails it's ok. Zapier has a free tier and since I don't have much traffic it's a decent workaround for time being.
The new Firebase SDK v3 appears to support email address verification, see here (put your own project id in the link) but it doesn't appear to be documented yet.
I have asked the question on SO here
See #SamQuayle's answer there with this link to the official docs.
As noted by various others Firebase does now support account related emails but even better, as of 10 days ago or so it also supports sending any kind of email via Firebase Functions. Lots of details in the docs and example code here.
I used following code to check the email verification after creating new account.
let firAuth = FIRAuth.auth()
firAuth?.addAuthStateDidChangeListener { auth, user in
if let loggedUser = user {
if loggedUser.emailVerified == false {
loggedUser.sendEmailVerificationWithCompletion({ (error) in
print("error:\(error)")
})
}
else {
print(loggedUser.email)
}
} else {
// No user is signed in.
print("No user is signed in.")
}
}
I used MandrillApp. You can create an API key that only allows sending of a template. This way even thought your key is exposed it can't really be abused unless someone wants to fire off tonnes of welcome emails for you.
That was a hack to get myself off the ground. I'm now enabling CORS from a EC2 that uses the token to verify that the user exists before extending them a welcome via SES.

Categories

Resources