Use Smart Lock with Firebase Auth Email+Password accounts - android

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

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.

FirebaseAuthUserCollisionException vs FirebaseAuthInvalidUserException when user account is disabled?

I am currently developing a android app which uses FirebaseAuth to control users.
The users can sign-in either using email or Google.
Considering this, i am catching some exceptions in order to handle any problems with the authentication system.
Most of these work fine. This is my code:
when(task.exception!!){
is FirebaseAuthEmailException -> {
errorDialogBuilder.setTitle(R.string.exception_email_title)
errorDialogBuilder.setMessage(getString(R.string.exception_email_msg, task.exception!!.localizedMessage))
}
is FirebaseAuthInvalidCredentialsException -> {
errorDialogBuilder.setTitle(R.string.exception_invalid_credentials_title)
errorDialogBuilder.setMessage(getString(R.string.exception_invalid_credentials_msg, task.exception!!.localizedMessage))
}
is FirebaseAuthInvalidUserException -> {
val invalidUserException = task.exception!! as FirebaseAuthInvalidUserException
when (invalidUserException.errorCode) {
"ERROR_USER_DISABLED" -> {
errorDialogBuilder.setTitle(R.string.exception_user_disabled_title)
errorDialogBuilder.setMessage(R.string.exception_user_disabled_msg)
errorDialogBuilder.setNeutralButton(R.string.more, null)
}
"ERROR_USER_NOT_FOUND" -> {
errorDialogBuilder.setTitle(R.string.exception_user_not_found_title)
errorDialogBuilder.setMessage(getString(R.string.exception_user_not_found_msg, fragmentView.email_input_signin.text.toString()))
errorDialogBuilder.setNeutralButton(R.string.action_createnew_account) { _, _ ->
viewModel.createUser(fragmentView.email_input_signin.text.toString(), fragmentView.email_password.text.toString())
}
}
else -> {
errorDialogBuilder.setTitle(invalidUserException.errorCode)
errorDialogBuilder.setMessage(invalidUserException.localizedMessage)
}
}
}
}
I had no problems when using this code for development purposes. But I found out something very weird:
I am playing around with disabling users; This issue ocurred: Whenever I disable a google account, not a FirebaseAuthInvalidUserException is thrown with code ERROR_USER_DISABLED as when trying to sign in with a disabled email-based account, but a FirebaseUserCollissionException which has the exact same message as a FirebaseAuthInvalidUserException with code ERROR_USER_DISABLED:
com.google.firebase.auth.FirebaseAuthUserCollisionException: The user account has been disabled by an administrator.
at com.google.firebase.auth.api.internal.zzdx.zza(com.google.firebase:firebase-auth##19.1.0:42)
at com.google.firebase.auth.api.internal.zzfa.zza(com.google.firebase:firebase-auth##19.1.0:19)
at com.google.firebase.auth.api.internal.zzet.zza(com.google.firebase:firebase-auth##19.1.0:34)
at com.google.firebase.auth.api.internal.zzev.zza(com.google.firebase:firebase-auth##19.1.0:98)
at com.google.firebase.auth.api.internal.zzev.zza(com.google.firebase:firebase-auth##19.1.0:85)
at com.google.firebase.auth.api.internal.zzed.zza(com.google.firebase:firebase-auth##19.1.0:43)
at com.google.android.gms.internal.firebase_auth.zza.onTransact(com.google.firebase:firebase-auth##19.1.0:13)
at android.os.Binder.execTransactInternal(Binder.java:1021)
at android.os.Binder.execTransact(Binder.java:994)
This is how it looks normally, when trying to use an disabled email-based account(this is handled&this works):
com.google.firebase.auth.FirebaseAuthInvalidUserException: The user account has been disabled by an administrator.
at com.google.firebase.auth.api.internal.zzdx.zza(com.google.firebase:firebase-auth##19.1.0:6)
at com.google.firebase.auth.api.internal.zzfa.zza(com.google.firebase:firebase-auth##19.1.0:21)
at com.google.firebase.auth.api.internal.zzet.zza(com.google.firebase:firebase-auth##19.1.0:34)
at com.google.firebase.auth.api.internal.zzev.zza(com.google.firebase:firebase-auth##19.1.0:74)
at com.google.firebase.auth.api.internal.zzed.zza(com.google.firebase:firebase-auth##19.1.0:18)
at com.google.android.gms.internal.firebase_auth.zza.onTransact(com.google.firebase:firebase-auth##19.1.0:13)
at android.os.Binder.execTransactInternal(Binder.java:1021)
at android.os.Binder.execTransact(Binder.java:994)
Why are there two different exceptions being used here? And why would firebase throw a FirebaseAuthUserCollisionException when the users account is disabled, as, from what i know from the docs, the FirebaseAuthUserCollisionException is only thrown when there are user accounts conflicting?
In addition:
Brief extract from the firebase docs:
FirebaseAuthUserCollisionException
public final class FirebaseAuthUserCollisionException extends FirebaseAuthException
Thrown when an operation on a FirebaseUser instance couldn't be
completed due to a conflict with another existing user.
That's weird. I read the Google API for Android docs - and what you said was correct - FirebaseAuthUserCollisionException should only be thrown when there is a conflict between Firebase users, particularly if both share the same credential accidentally, but I think Firebase Auth does not allow that and it can detect if the credential between two users or more are similar right from the start.
I think that Firebase Auth tries to re-register/re-add the disabled Google account into the system when you use it to sign in. Since the Google account is disabled, Firebase Auth may consider the account invalid - yet the account still remain and recorded in the auth system - hence if you are trying to sign in with the previously disabled Google account, Firebase Auth tries to re-add that and collide with the previous record of disabled Google account.
There is also a chance that this may be a bug/glitch/error in Firebase Auth side.
If you are asking about difference, I think it's more to the circumstances/conditions that caused either of those exceptions to occur; most exceptions are similar, it is the purpose and cause of error that makes the difference.
Hope this helps.

Is it possible to detect FirebaseAuthUserCollisionException when use FirebaseAuth?

I has some problem when I try to create new account[email, password]in my app using FirebaseAuth. I want to detect if email is already use in other account. For example, I want to create account a#b.c in my app, but I'm already using this email to login by facebook. So, Is it possible to detect FirebaseAuthUserCollisionException in Firebase.
This is my code.
mAuth.createUserWithEmailAndPassword(edt1.getText().toString(), edt2.getText().toString())
.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, "createUserWithEmail:success");
FirebaseUser user = mAuth.getCurrentUser();
startActivity( new Intent( NewRegisterForEmali.this, NewLoginActivity.class));
finish();
}else if(task.getException().equals("com.google.firebase.auth.FirebaseAuthUserCollisionException")){
Log.d(TAG, "Collision!");
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "createUserWithEmail:failure", task.getException());
Toast.makeText(NewRegisterForEmali.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
task.getException();
}
// ...
}
});
Logcat:
com.google.firebase.auth.FirebaseAuthUserCollisionException: The email address is already in use by another account.
at com.google.android.gms.internal.zzeaw.zzaw(Unknown Source)
at com.google.android.gms.internal.zzdzu.zza(Unknown Source)
at com.google.android.gms.internal.zzebh.zzax(Unknown Source)
at com.google.android.gms.internal.zzebk.onFailure(Unknown Source)
at com.google.android.gms.internal.zzeay.onTransact(Unknown Source)
at android.os.Binder.execTransact(Binder.java:565)
maybe this link will help
Dealing with Email address already in use - Firebase Authentication
answer by #alex mamo
The first one is to verify if the email address exists and than 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. With other words, if a user signs up with gmail and then signs up with Facebook and he has the same email address, than 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 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, 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.
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

Firebase Authentication with Google issue

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

Categories

Resources