Logout in Azure AD B2C Android using MSAL - android

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

Related

Handling FirebaseAuthUserCollisionException when using FirebaseUI

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.

Android dynamic link doesn't survive fresh app installation

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.

Branch IO - Show referral parameters in custom event

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.

Google Ads attribution in Firebase SDK

We have recently switched from Google Analytics SDK to Firebase SDK in our Android app.
Before that, we used INSTALL_REFERRER to get the user's source and medium.
Now we have started an App Campaign on Google Ads and INSTALL_REFERRER no longer works, though conversions keep coming.
How do we use Firebase SDK to know that user came from Google Ads campaign?
I think you'll want to use the Play Install Referrer API.
The link above cautions that the install referrer information will be available for 90 days and to only invoke the API during the first run of the app to avoid unnecessary API calls.
Here's an example (taking from the link above), assuming you have added the library to your build.gradle file:
Initialization:
private lateinit var referrerClient: InstallReferrerClient
...
referrerClient = InstallReferrerClient.newBuilder(this).build()
referrerClient.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
when (responseCode) {
InstallReferrerResponse.OK -> {
// Connection established
}
InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> {
// API not available on the current Play Store app
}
InstallReferrerResponse.SERVICE_UNAVAILABLE -> {
// Connection could not be established
}
}
}
override fun onInstallReferrerServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
Getting the referrer:
val response: ReferrerDetails = referrerClient.installReferrer
val referrer = response.installReferrer
val clickTimestamp = response.referrerClickTimestampSeconds
val installTimestamp = response.installBeginTimestampSeconds
Wrapping Up:
referrerClient.endConnection()
Checking for gclid (Google Ads)
if ("gclid" in referrer) {
//report to Firebase Analytics
} else {
//do something else
}
In this way, we will determine gclid only if the user has clicked on the advertisement in the browser, but if he clicked on the advertisement in the play market, then nothing will work.

Consent SDK : consentStatus become UNKNOWN again

I'm currently trying to integrate the Google Consent SDK.
something is happening that I don't understand with the onConsentInfoUpdated(consentStatus:ConsentStatus) function
User opens the app for the first time and makes a choice when the popup is diplayed
Consent.Status -> UNKNOWN
User kills / re-opens the app.
Sometimes Consent.Status -> PERSONALIZED or Consent.Status -> UNKNOWN and user makes a choice again.
here my code:
fun requestConsent(activity: Activity) {
val consentInformation = ConsentInformation.getInstance(activity)
val publisherIds = arrayOf(activity.getString(R.string.admob_publisher_id))
consentInformation.requestConsentInfoUpdate(publisherIds, object : ConsentInfoUpdateListener {
override fun onConsentInfoUpdated(consentStatus: ConsentStatus) {
Log.d("test--", consentStatus.toString())
when (consentStatus) {
ConsentStatus.PERSONALIZED -> showPersonalizedAds()
ConsentStatus.NON_PERSONALIZED -> showNonPersonalizedAds()
ConsentStatus.UNKNOWN -> loadConsentForm(activity)
}
}
override fun onFailedToUpdateConsentInfo(errorDescription: String) {
//onFailedToUpdateConsentInfo()
}
})
}
private fun showPersonalizedAds() {
ConsentInformation.getInstance(activity).consentStatus = ConsentStatus.PERSONALIZED
}
private fun showNonPersonalizedAds() {
ConsentInformation.getInstance(activity).consentStatus = ConsentStatus.NON_PERSONALIZED
}
here my logs:
This is the first result that came up on Google and I tried your solution but it didn't resolve the issue. In my case I was using:
com.google.android.ads.consent:consent-library:1.0.6
The issue was resolved by upgrading to:
com.google.android.ads.consent:consent-library:1.0.8
Google removed the restriction on using Google rendered consent form with the commonly used set of ad technology providers in 1.0.6 and changed logic for re-consent prompts in 1.0.8 - as seen here: https://github.com/googleads/googleads-consent-sdk-android/blob/master/ChangeLog.md
Could it be that you also changed your library version without realising?
by default, all advertisers are selected in the admob console. That's why I was able to solve my problem by going to the blocking settings section for users in the European Union.
click Blocking controls and then EU user consent
https://support.google.com/admob/answer/7666519#providers

Categories

Resources