I am currently using Firebase-UI for Android to implement authentication flow in my app. I currently have Google, Facebook, and Email auth providers enabled. My Android app is built using Jetpack Compose and I'm using rememberLauncherForActivityResult to launch the login intent. Everything is working as expected with the normal flow.
However, when I try to use my Facebook login with the same email that I have previously authenticated using Gmail, I am getting the below error.
A sign-in error occurred.
com.google.firebase.auth.FirebaseAuthUserCollisionException: This credential is already associated with a different user account.**
at com.google.android.gms.internal.firebase-auth-api.zzxc.zzb(com.google.firebase:firebase-auth##21.1.0:4)
at com.google.android.gms.internal.firebase-auth-api.zzya.zza(com.google.firebase:firebase-auth##21.1.0:7)
at com.google.android.gms.internal.firebase-auth-api.zzyb.zzl(com.google.firebase:firebase-auth##21.1.0:1)
at com.google.android.gms.internal.firebase-auth-api.zzxy.zzq(com.google.firebase:firebase-auth##21.1.0:3)
at com.google.android.gms.internal.firebase-auth-api.zzxy.zze(com.google.firebase:firebase-auth##21.1.0:1)
at com.google.android.gms.internal.firebase-auth-api.zzxa.zze(com.google.firebase:firebase-auth##21.1.0:1)
at com.google.android.gms.internal.firebase-auth-api.zzvf.zzd(com.google.firebase:firebase-auth##21.1.0:8)
at com.google.android.gms.internal.firebase-auth-api.zzuf.zzb(com.google.firebase:firebase-auth##21.1.0:2)
at com.google.android.gms.internal.firebase-auth-api.zzyj.zzb(com.google.firebase:firebase-auth##21.1.0:12)
at com.google.android.gms.internal.firebase-auth-api.zzyj.zza(com.google.firebase:firebase-auth##21.1.0:14)
at com.google.android.gms.internal.firebase-auth-api.zzxp.zzq(com.google.firebase:firebase-auth##21.1.0:4)
at com.google.android.gms.internal.firebase-auth-api.zzug.zzb(com.google.firebase:firebase-auth##21.1.0:4)
at com.google.android.gms.internal.firebase-auth-api.zzvf.zzM(com.google.firebase:firebase-auth##21.1.0:5)
at com.google.android.gms.internal.firebase-auth-api.zzvf.zzs(com.google.firebase:firebase-auth##21.1.0:4)
at com.google.android.gms.internal.firebase-auth-api.zzxb.zzm(com.google.firebase:firebase-auth##21.1.0:6)
at com.google.android.gms.internal.firebase-auth-api.zzvr.zzc(com.google.firebase:firebase-auth##21.1.0:1)
at com.google.android.gms.internal.firebase-auth-api.zzyc.run(com.google.firebase:firebase-auth##21.1.0:1)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
at java.lang.Thread.run(Thread.java:1012)
I would like someway to handle this exception but I am not able to find a way. Am I missing something obvious?
Here is my implementation
ProfileViewModel.kt
override fun buildLoginIntent(): Intent {
val authUILayout = AuthMethodPickerLayout.Builder(R.layout.auth_ui)
.setGoogleButtonId(R.id.btn_gmail)
.setEmailButtonId(R.id.btn_email)
.setFacebookButtonId(R.id.btn_facebook)
.build()
val googleScopes = arrayListOf(
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email"
)
val intent = AuthUI.getInstance().createSignInIntentBuilder()
.setAvailableProviders(
listOf(
AuthUI.IdpConfig.EmailBuilder().build(),
AuthUI.IdpConfig.GoogleBuilder().setScopes(googleScopes).build(),
AuthUI.IdpConfig.FacebookBuilder().build()
)
)
.enableAnonymousUsersAutoUpgrade()
.setLogo(R.mipmap.ic_launcher)
.setAuthMethodPickerLayout(authUILayout)
.build()
return intent
}
#SuppressLint("RestrictedApi")
override fun onLoginResult(result: FirebaseAuthUIAuthenticationResult) {
// Handle result
}
ProfileUI.kt
Composable UI where I launch the intent
val loginLauncher = rememberLauncherForActivityResult(
profileViewModel.buildLoginActivityResult()
) { result ->
if (result != null) {
profileViewModel.onLoginResult(result = result)
}
}
if (isAnonymousUser) {
SignInUI() {
loginLauncher.launch(profileViewModel.buildLoginIntent())
}
}
However, when I try to use my Facebook login with the same email that I have previously authenticated using Gmail, I am getting the below error.
This credential is already associated with a different user account.
That's the expected behavior since your user was first time authenticated with Google. When you authenticate a user in Firebase with Google, there is a user created that contains the data that corresponds to that particular provider. If you try to use that data to authenticate to Facebook, the authentication will fail, because the user was already created with another provider, hence the error.
A solution for this kind of situation would be to check if the user already exists, before authenticating it. If the user already exists, then you have to read the provider and display a message to the user, so it can choose the right authentication provider.
Another option might be to allow 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.
Related
This question already has answers here:
Firebase kicks out current user
(19 answers)
Firebase create user without sign in [duplicate]
(1 answer)
Closed 12 months ago.
How to create an user without sign in with Firebase Auth Android ?
It looks like the accepted solution here is a bit outdated : Firebase create user without sign in
Nowadays, is there any other way ?
Ok, answering myself after some research.
It looks like the situation is the same today. We still have to create an other instance of FirebaseAuth to avoid triggering auth state on the default FirebaseAuth instance you use.
But i suggest a simpler way to do it, without having to provide api key, application id...etc by hand by just using the FirebaseOptions of the default instance.
val firebaseDefaultApp = Firebase.auth.app
val signUpAppName = firebaseDefaultApp.name + "_signUp"
val signUpApp = try {
FirebaseApp.initializeApp(
context,
firebaseDefaultApp.options,
signUpAppName
)
} catch (e: IllegalStateException) {
// IllegalStateException is throw if an app with the same name has already been initialized.
FirebaseApp.getInstance(signUpAppName)
}
// Here is the instance you can use to sign up without triggering auth state on the default Firebase.auth
val signUpFirebaseAuth = Firebase.auth(signUpApp)
How to use ?
signUpFirebaseAuth
.createUserWithEmailAndPassword(email, password)
.addOnSuccessListener {
// Optional, you can send verification email here if you need
// As soon as the sign up with sign in is over, we can sign out the current user
firebaseAuthSignUp.signOut()
}
.addOnFailureListener {
// Log
}
I'm trying to implement referral in my app with Firebase Dynamic link.
The happy path would be:
Link is shared with a JWT token as parameter
Another user clicks on the link, doesn't have the app so it goes to the Playstore
After app was installed, at app startup, the JWT token is retrieved trough parameters and stored locally
New signup is complete, the token is sent to the backend to activate referrer's reward
Problem is that in this scenario, step 3 doesn't find any data at first app startup, whereas it is found when I click on the link anew after the app has been installed.
This is the way the link is generated for the referrer:
val parameters = new DynamicLink.SocialMetaTagParameters.Builder()
.setImageUrl(...)
.setTitle(...)
.build();
val link = Uri.parse("https://my.domain.com/path/?token=${jwtToken}");
val dynamicLink = FirebaseDynamicLinks.getInstance().createDynamicLink()
.setLink(link)
.setSocialMetaTagParameters(parameters)
.setDomainUriPrefix("https://my.domain.com")
.setAndroidParameters(DynamicLink.AndroidParameters.Builder().build()))
.setIosParameters(...)
.setNavigationInfoParameters(
DynamicLink.NavigationInfoParameters.Builder()
.setForcedRedirectEnabled(true)
.build
).buildDynamicLink()
The link is shorten by:
FirebaseDynamicLinks.getInstance().createDynamicLink()
.setDomainUriPrefix("https://my.domain.com/")
.setLongLink(dynamicLink.getUri())
.buildShortDynamicLink()
.addOnCompleteListener(activity, task -> {
if (task.isSuccessful() && task.getResult() != null) {
linkCallback.success(task.getResult().getShortLink().toString());
} else {
linkCallback.failure(task.getException());
}
})
At app's opening, link is read in the onResume() method of the starting activity :
override fun onResume() {
super.onResume()
FirebaseDynamicLinks.getInstance()
.getDynamicLink(getIntent()) // getIntent() refers to the Activity's method
.addOnSuccessListener(activity) { data -> // Get deep link from result (may be null if no link is found)
val isReferralLink = data?.link?.toString()?.startsWith("https://my.domain.com/path") == true
val jwt = data?.link?.getQueryParameter("token")
// Here, data is null. <-------
// Other attempt :
data?.let { aiData ->
FirebaseAppInvite.getInvitation(aiData)?.let { result ->
// Here result is still null
}
}
}
}
I've seen on a stack overflow thread that it doesn't survive to beta track install, so I tried to leave the beta and use remote config to hide it in production track, but I have not been able see any difference.
And the version I'm using is :
implementation 'com.google.firebase:firebase-dynamic-links:21.0.0'
implementation 'com.google.firebase:firebase-analytics:20.0.0'
implementation 'com.google.firebase:firebase-invites:17.0.0'
Question:
Is there something I'm missing here ?
Thank you in advance for your help !
EDIT:
In this scenario the link and token are correctly found.
Link is shared with a JWT token as parameter
Another user clicks on the link, doesn't have the app so it goes to the Playstore
After app was installed, the second user clicks again on the link and opens the app for the first time
Ok so after a few days of testing and researches, I found out why it was not working.
FirebaseDynamicLinks.getInstance()
.getDynamicLink(getIntent())
.addOnSuccessListener(activity) { data ->
// Some data reading
}
In this case I was using the addOnSuccessListener() with an activity as parameter, which makes it lifecycle aware. In my app, a new user (fresh install) will be redirected to an onboarding activity, so the listener's activity is paused, and the callback is never fired.
=> TLDR: Removing this activity parameter solved my problem.
i have integrated Azure AD B2C on my mobile App using MSAL library. I have created the user flows for SignIn and SignUp. I'am getting the access token once the authentication is success. so i have my logout button designed on my Mobile App on one of my Activity Page, so once i click on my logout button designed on App, i need to get the user signedOut from the App. so how can we do the SignOut in AZURE AD B2C MSAL on Android?
is it ok even if i clear the access token saved on my preference? or is there any other way to signout from Azure side provideb by MSAL library?
The MSAL library provides a logout method that clears the cache in browser storage and sends a sign-out request to Azure Active Directory (Azure AD). Request will be done against the end_session_endpoint URL obtained from the B2C policy metadata. Keep in mind single sign out is supported only by custom policies and that it's scoped to the same browser, not device.
For a Native Android mobile app please use the signout function in Ms Document
To remove this user from the cache, you must call removeAccount() for each policy.
PublicClientApplication publicClient = MyApplication.getInstance().getPublicClient();
User currentUser = Helpers.getUserByPolicy(publicClient.getUsers(), Constant.SIGN_UP_POLICY);
publicClient.remove(currentUser);
//Load account using publicClientApplication
private fun loadAccounts(mMultipleAccountApp: IMultipleAccountPublicClientApplication) {
mMultipleAccountApp.getAccounts(object: IPublicClientApplication.LoadAccountsCallback {
override fun onTaskCompleted(result: List<IAccount>) {
val accountList: List<IAccount> = result
removeAccounts(mMultipleAccountApp, accountList)
}
override fun onError(exception: MsalException) {
Log.d("Error",exception.message)
}
})
}
//call remove account
private fun removeAccounts(
mMultipleAccountApp: IMultipleAccountPublicClientApplication,
accountList: List<IAccount>
) {
val removeCallback =
object : IMultipleAccountPublicClientApplication.RemoveAccountCallback {
override fun onRemoved() {
// Redirect to login
}
override fun onError(exception: MsalException) {
Log.d("Error",exception.message)
}
}
B2CUser.signOutAsync(
accountList,
mMultipleAccountApp, removeCallback
)
}
I have a problem when I loggin the first time, imageUrl from profile user can't load, then when I close the app and start it again, I get the imageUrl because the user is already logged. I don't know why I can not get the imageUrl at beggining if user logged.
FirebaseNoSignedInUserException: Please sign in before trying to get a token.
E/StorageException: StorageException has occurred.
Object does not exist at location.
Code: -13010 HttpResult: 404
private val auth = FirebaseAuth.getInstance()
private val storeRef = FirebaseStorage.getInstance()
override suspend fun initProfile() : Resource<Uri> {
val imageUriUrl = storeRef.getReferenceFromUrl("gs://pruebamultimedialab.appspot.com/")
.child("images/${auth.currentUser?.uid}")
.downloadUrl.await()
return Resource.Success(imageUriUrl)
}
This is my code to get ImageUrl from Firebase and its working fine.
The problem is when I signIn the firts time. I don't know what to do.
Also the rules from cloud:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write;
}
}
}
Using Navigation component might not really solve the issue. What is
mostly done is to have a different activity for authentication like
Login, SignUp, OnBoarding. Then takes you to the main activity of the
app. – princessdharmy
1 Activity to hold : Login, Register and Reset fragments
2 activity to hold : profile, Main, etc.. fragments of main app.
Yes, I'm doing like that, is the best way to do it, because I saw conditional Navigation, and is a little complicated. Thanks for your answer #princessdharmy and #Sk Suraj.
In my app I am tracking app install referrals using Branch and I also have a custom event called SIGN_UP which gets sent after the sign up request completes.|
So after using a branch link (fb one in particular) to install and sign up in my app, even though I get normal campaign data in the INSTALL event, I do not seem to get any relevant data in my custom one:
Screenshot of dashboard webhook events when filtered by AAID (see how columns Campaign, Ad Partner 3P, Ad Partner, Channel, Feature are empty in the first row.)
Any reason why this happens?
Code used:
In my Application class:
#Override
public void onCreate() {
super.onCreate();
// ...
Branch.getAutoInstance(this);
// ...
}
In my launcher Activity:
private val callback = Branch.BranchReferralInitListener { referringParams, error ->
if (error == null) {
Timber.i("BRANCH SDK success: %s", referringParams.toString())
} else {
Timber.e("BRANCH SDK error: %s", error.message)
}
}
override fun onStart() {
super.onStart()
Branch.sessionBuilder(this).withCallback(callback).withData(this.intent?.data).init()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Branch.sessionBuilder(this).withCallback(callback).reInit()
}
When sending a SIGN_UP event:
Branch.getInstance().setIdentity(memberId)
BranchEvent("SIGN_UP")
.addCustomDataProperty("UDID", udid)
.logEvent(activity)
Branch version: 5.0.1
For Facebook as Ad Network, Branch has the following data limitations
We cannot send device-level Facebook attribution data to third parties.
We cannot send events attributed to Facebook via Data Integrations. Please instead consider analyzing this data in-house (using Webhooks, the Daily Export API, or CSV Exports), or using the Branch Dashboard for all of your analytics and attribution needs.
This data is also not returned in the deeplink session initialization callback within the app. Also, you must have signed Facebook's "Advanced Mobile Measurement" agreement ("Data Use Terms for Advanced Mobile App Measurement") to view this data.