From 50f9e5b4ec970dd661a100460c0dd633f77ae74f Mon Sep 17 00:00:00 2001 From: demolaf Date: Fri, 15 May 2026 11:27:48 +0100 Subject: [PATCH 1/4] fix(auth): log out before Facebook sign-in to clear stale cached token --- .../FacebookAuthProvider+FirebaseAuthUI.kt | 1 + .../FacebookAuthProviderFirebaseAuthUI.kt | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt index 28ef45636..3927498d9 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt @@ -150,6 +150,7 @@ internal suspend fun FirebaseAuthUI.signInWithFacebook( updateAuthState( AuthState.Loading("Signing in with facebook...") ) + (testLoginManagerProvider ?: credentialProvider).logOut() val profileData = provider.fetchFacebookProfile(accessToken) val credential = credentialProvider.getCredential(accessToken.token) signInAndLinkWithCredential( diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt index 1e48bae90..4042d8af8 100644 --- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt +++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt @@ -102,6 +102,46 @@ class FacebookAuthProviderFirebaseAuthUITest { } } + @Test + @Config(manifest = Config.NONE, qualifiers = "night") + fun `signInWithFacebook - calls logOut before sign in to clear stale cached token`() = runTest { + val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth) + val provider = spy(AuthProvider.Facebook()) + val config = authUIConfiguration { + context = applicationContext + providers { + provider(provider) + } + } + + val mockAccessToken = mock { + on { token } doReturn "random-token" + } + val mockCredential = mock() + val mockUser = mock() + val mockAuthResult = mock() + whenever(mockAuthResult.user).thenReturn(mockUser) + whenever(mockUser.isEmailVerified).thenReturn(true) + whenever(mockUser.providerData).thenReturn(emptyList()) + val taskCompletionSource = TaskCompletionSource() + taskCompletionSource.setResult(mockAuthResult) + whenever(mockFirebaseAuth.signInWithCredential(mockCredential)) + .thenReturn(taskCompletionSource.task) + doReturn(null).whenever(provider).fetchFacebookProfile(any()) + whenever(mockFBAuthCredentialProvider.getCredential("random-token")) + .thenReturn(mockCredential) + + instance.signInWithFacebook( + context = applicationContext, + config = config, + provider = provider, + accessToken = mockAccessToken, + credentialProvider = mockFBAuthCredentialProvider + ) + + verify(mockFBAuthCredentialProvider).logOut() + } + @Test @Config(manifest = Config.NONE, qualifiers = "night") fun `signInWithFacebook - successful sign in signs user in and emits Success authState`() = runTest { From f3c0ddc2d0fa4321daad6aa20ddcfcc90e7df1af Mon Sep 17 00:00:00 2001 From: demolaf Date: Fri, 15 May 2026 11:58:35 +0100 Subject: [PATCH 2/4] updates --- .../FacebookAuthProvider+FirebaseAuthUI.kt | 3 +- .../FacebookAuthProviderFirebaseAuthUI.kt | 40 ------------------- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt index 3927498d9..9c6d9c561 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt @@ -56,6 +56,7 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( context: Context, config: AuthUIConfiguration, provider: AuthProvider.Facebook, + loginManagerProvider: AuthProvider.Facebook.LoginManagerProvider = AuthProvider.Facebook.DefaultLoginManagerProvider(), ): () -> Unit { val coroutineScope = rememberCoroutineScope() val callbackManager = remember { CallbackManager.Factory.create() } @@ -114,6 +115,7 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( updateAuthState( AuthState.Loading("Signing in with facebook...") ) + (testLoginManagerProvider ?: loginManagerProvider).logOut() launcher.launch(provider.scopes) } } @@ -150,7 +152,6 @@ internal suspend fun FirebaseAuthUI.signInWithFacebook( updateAuthState( AuthState.Loading("Signing in with facebook...") ) - (testLoginManagerProvider ?: credentialProvider).logOut() val profileData = provider.fetchFacebookProfile(accessToken) val credential = credentialProvider.getCredential(accessToken.token) signInAndLinkWithCredential( diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt index 4042d8af8..1e48bae90 100644 --- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt +++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt @@ -102,46 +102,6 @@ class FacebookAuthProviderFirebaseAuthUITest { } } - @Test - @Config(manifest = Config.NONE, qualifiers = "night") - fun `signInWithFacebook - calls logOut before sign in to clear stale cached token`() = runTest { - val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth) - val provider = spy(AuthProvider.Facebook()) - val config = authUIConfiguration { - context = applicationContext - providers { - provider(provider) - } - } - - val mockAccessToken = mock { - on { token } doReturn "random-token" - } - val mockCredential = mock() - val mockUser = mock() - val mockAuthResult = mock() - whenever(mockAuthResult.user).thenReturn(mockUser) - whenever(mockUser.isEmailVerified).thenReturn(true) - whenever(mockUser.providerData).thenReturn(emptyList()) - val taskCompletionSource = TaskCompletionSource() - taskCompletionSource.setResult(mockAuthResult) - whenever(mockFirebaseAuth.signInWithCredential(mockCredential)) - .thenReturn(taskCompletionSource.task) - doReturn(null).whenever(provider).fetchFacebookProfile(any()) - whenever(mockFBAuthCredentialProvider.getCredential("random-token")) - .thenReturn(mockCredential) - - instance.signInWithFacebook( - context = applicationContext, - config = config, - provider = provider, - accessToken = mockAccessToken, - credentialProvider = mockFBAuthCredentialProvider - ) - - verify(mockFBAuthCredentialProvider).logOut() - } - @Test @Config(manifest = Config.NONE, qualifiers = "night") fun `signInWithFacebook - successful sign in signs user in and emits Success authState`() = runTest { From 5944e7047641980eb05660ed1d9864b70b1760e9 Mon Sep 17 00:00:00 2001 From: demolaf Date: Fri, 15 May 2026 12:24:08 +0100 Subject: [PATCH 3/4] updates --- .../FacebookAuthProviderFirebaseAuthUI.kt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt index 1e48bae90..409e600fc 100644 --- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt +++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt @@ -16,9 +16,11 @@ package com.firebase.ui.auth.configuration.auth_provider import android.content.Context import android.net.Uri +import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.core.app.ApplicationProvider import com.facebook.AccessToken import com.facebook.FacebookException +import com.facebook.FacebookSdk import com.firebase.ui.auth.AuthException import com.firebase.ui.auth.AuthState import com.firebase.ui.auth.FirebaseAuthUI @@ -40,6 +42,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -61,6 +64,9 @@ import org.robolectric.annotation.Config @Config(manifest = Config.NONE) class FacebookAuthProviderFirebaseAuthUITest { + @get:Rule + val composeTestRule = createComposeRule() + @Mock private lateinit var mockFirebaseAuth: FirebaseAuth @@ -78,6 +84,11 @@ class FacebookAuthProviderFirebaseAuthUITest { applicationContext = ApplicationProvider.getApplicationContext() + FacebookSdk.setApplicationId("fake-app-id") + FacebookSdk.setClientToken("fake-client-token") + @Suppress("DEPRECATION") + FacebookSdk.sdkInitialize(applicationContext) + FirebaseApp.getApps(applicationContext).forEach { app -> app.delete() } @@ -102,6 +113,40 @@ class FacebookAuthProviderFirebaseAuthUITest { } } + @Test + @Config(manifest = Config.NONE, qualifiers = "night") + fun `rememberSignInWithFacebookLauncher - calls logOut before launching to clear stale token`() { + val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth) + val provider = AuthProvider.Facebook() + val config = authUIConfiguration { + context = applicationContext + providers { + provider(provider) + } + } + + var launcher: (() -> Unit)? = null + + composeTestRule.setContent { + launcher = instance.rememberSignInWithFacebookLauncher( + context = applicationContext, + config = config, + provider = provider, + loginManagerProvider = mockFBAuthCredentialProvider, + ) + } + + composeTestRule.runOnIdle { + try { + launcher?.invoke() + } catch (_: Exception) { + // launcher.launch() may throw in test environment — that's expected + } + } + + verify(mockFBAuthCredentialProvider).logOut() + } + @Test @Config(manifest = Config.NONE, qualifiers = "night") fun `signInWithFacebook - successful sign in signs user in and emits Success authState`() = runTest { From cc4644d61052431a678e34e604511b1deca4b0fc Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 18 May 2026 14:45:59 +0100 Subject: [PATCH 4/4] fix: wrap FB logout in try/catch to stop blocking sign-in launcher --- .../FacebookAuthProvider+FirebaseAuthUI.kt | 7 ++- .../FacebookAuthProviderFirebaseAuthUI.kt | 44 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt index 9c6d9c561..ac99ad747 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt @@ -46,6 +46,7 @@ import kotlinx.coroutines.launch * @param context Android context for DataStore access when saving credentials for linking * @param config The [AuthUIConfiguration] containing authentication settings * @param provider The [AuthProvider.Facebook] configuration with scopes and credential provider + * @param loginManagerProvider Provides logout operations to clear stale Facebook sessions * * @return A launcher function that starts the Facebook sign-in flow when invoked * @@ -115,7 +116,11 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( updateAuthState( AuthState.Loading("Signing in with facebook...") ) - (testLoginManagerProvider ?: loginManagerProvider).logOut() + try { + (testLoginManagerProvider ?: loginManagerProvider).logOut() + } catch (e: Exception) { + Log.w("FacebookAuthProvider", "Failed to clear Facebook session before sign in", e) + } launcher.launch(provider.scopes) } } diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt index 409e600fc..fe10118e6 100644 --- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt +++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt @@ -147,6 +147,50 @@ class FacebookAuthProviderFirebaseAuthUITest { verify(mockFBAuthCredentialProvider).logOut() } + @Test + @Config(manifest = Config.NONE, qualifiers = "night") + fun `rememberSignInWithFacebookLauncher - does not propagate stale token logout failure`() { + val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth) + val provider = AuthProvider.Facebook() + val config = authUIConfiguration { + context = applicationContext + providers { + provider(provider) + } + } + val logoutException = RuntimeException("logout failed") + doAnswer { + throw logoutException + }.whenever(mockFBAuthCredentialProvider).logOut() + + var launcher: (() -> Unit)? = null + var thrownException: Exception? = null + + composeTestRule.setContent { + launcher = instance.rememberSignInWithFacebookLauncher( + context = applicationContext, + config = config, + provider = provider, + loginManagerProvider = mockFBAuthCredentialProvider, + ) + } + + composeTestRule.runOnIdle { + try { + launcher?.invoke() + } catch (e: Exception) { + thrownException = e + } + } + + var exceptionInChain: Throwable? = thrownException + while (exceptionInChain != null) { + assertThat(exceptionInChain).isNotEqualTo(logoutException) + exceptionInChain = exceptionInChain.cause + } + verify(mockFBAuthCredentialProvider).logOut() + } + @Test @Config(manifest = Config.NONE, qualifiers = "night") fun `signInWithFacebook - successful sign in signs user in and emits Success authState`() = runTest {