diff --git a/app/src/main/java/com/firebaseui/android/demo/MainActivity.kt b/app/src/main/java/com/firebaseui/android/demo/MainActivity.kt
index 5a19beac4..a22e6028a 100644
--- a/app/src/main/java/com/firebaseui/android/demo/MainActivity.kt
+++ b/app/src/main/java/com/firebaseui/android/demo/MainActivity.kt
@@ -36,7 +36,7 @@ import com.google.firebase.FirebaseApp
*/
class MainActivity : ComponentActivity() {
companion object {
- private const val USE_AUTH_EMULATOR = true
+ private const val USE_AUTH_EMULATOR = false
private const val AUTH_EMULATOR_HOST = "10.0.2.2"
private const val AUTH_EMULATOR_PORT = 9099
}
diff --git a/auth/README.md b/auth/README.md
index ecc3208aa..6061eea06 100644
--- a/auth/README.md
+++ b/auth/README.md
@@ -138,6 +138,7 @@ If using Facebook Login, add your Facebook App ID to `strings.xml`:
YOUR_FACEBOOK_APP_ID
fbYOUR_FACEBOOK_APP_ID
+ CHANGE-ME
```
@@ -489,9 +490,6 @@ Configure Facebook Login with optional permissions:
```kotlin
val facebookProvider = AuthProvider.Facebook(
- // Optional: Facebook application ID (reads from strings.xml if not provided)
- applicationId = "YOUR_FACEBOOK_APP_ID",
-
// Optional: Permissions to request (default: ["email", "public_profile"])
scopes = listOf("email", "public_profile", "user_friends"),
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AuthProvider.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AuthProvider.kt
index 1b659a8fc..5cf392a8c 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AuthProvider.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AuthProvider.kt
@@ -485,7 +485,21 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
/**
* The OAuth 2.0 client ID for your server.
*/
- val serverClientId: String?,
+ var serverClientId: String?,
+
+ /**
+ * Whether to filter by authorized accounts.
+ * When true, only shows Google accounts that have previously authorized this app.
+ * Defaults to true, with automatic fallback to false if no authorized accounts found.
+ */
+ val filterByAuthorizedAccounts: Boolean = true,
+
+ /**
+ * Whether to enable auto-select for single account scenarios.
+ * When true, automatically selects the account if only one is available.
+ * Defaults to false for better user control.
+ */
+ val autoSelectEnabled: Boolean = false,
/**
* A map of custom OAuth parameters.
@@ -505,8 +519,9 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
" default_web_client_id string wasn't populated.",
R.string.default_web_client_id
)
+ serverClientId = context.getString(R.string.default_web_client_id)
} else {
- require(serverClientId.isNotBlank()) {
+ require(serverClientId!!.isNotBlank()) {
"Server client ID cannot be blank."
}
}
@@ -529,7 +544,7 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
val credential: AuthCredential,
val idToken: String,
val displayName: String?,
- val photoUrl: Uri?
+ val photoUrl: Uri?,
)
/**
@@ -567,7 +582,7 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
credentialManager: CredentialManager,
serverClientId: String,
filterByAuthorizedAccounts: Boolean,
- autoSelectEnabled: Boolean
+ autoSelectEnabled: Boolean,
): GoogleSignInResult
suspend fun clearCredentialState(
@@ -600,8 +615,10 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
.build()
val result = credentialManager.getCredential(context, request)
- val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(result.credential.data)
- val credential = GoogleAuthProvider.getCredential(googleIdTokenCredential.idToken, null)
+ val googleIdTokenCredential =
+ GoogleIdTokenCredential.createFrom(result.credential.data)
+ val credential =
+ GoogleAuthProvider.getCredential(googleIdTokenCredential.idToken, null)
return GoogleSignInResult(
credential = credential,
@@ -624,11 +641,6 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
* Facebook Login provider configuration.
*/
class Facebook(
- /**
- * The Facebook application ID.
- */
- val applicationId: String? = null,
-
/**
* The list of scopes (permissions) to request. Defaults to email and public_profile.
*/
@@ -653,18 +665,26 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
)
}
- if (applicationId == null) {
- Preconditions.checkConfigured(
- context,
- "Facebook provider unconfigured. Make sure to " +
- "add a `facebook_application_id` string or provide applicationId parameter.",
- R.string.facebook_application_id
- )
- } else {
- require(applicationId.isNotBlank()) {
- "Facebook application ID cannot be blank"
- }
- }
+ Preconditions.checkConfigured(
+ context,
+ "Facebook provider unconfigured. Make sure to " +
+ "add a `facebook_application_id` string to your strings.xml",
+ R.string.facebook_application_id
+ )
+
+ Preconditions.checkConfigured(
+ context,
+ "Facebook provider unconfigured. Make sure to " +
+ "add a `facebook_login_protocol_scheme` string to your strings.xml",
+ R.string.facebook_login_protocol_scheme
+ )
+
+ Preconditions.checkConfigured(
+ context,
+ "Facebook provider unconfigured. Make sure to " +
+ "add a `facebook_client_token` string to your strings.xml",
+ R.string.facebook_client_token
+ )
}
/**
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt
index 2afdb3599..4d18cb0a9 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt
@@ -133,14 +133,48 @@ internal suspend fun FirebaseAuthUI.signInWithGoogle(
}
}
- val result =
+ // Try with configured filterByAuthorizedAccounts setting
+ // If default (true), fallback to false if no authorized accounts found
+ // See: https://developer.android.com/identity/sign-in/credential-manager-siwg#siwg-button
+ val result = if (provider.filterByAuthorizedAccounts) {
+ // Default behavior: Try authorized accounts first, fallback to all accounts
+ try {
+ (testCredentialManagerProvider ?: credentialManagerProvider).getGoogleCredential(
+ context = context,
+ credentialManager = CredentialManager.create(context),
+ serverClientId = provider.serverClientId!!,
+ filterByAuthorizedAccounts = true,
+ autoSelectEnabled = provider.autoSelectEnabled
+ )
+ } catch (e: NoCredentialException) {
+ // No authorized accounts found, try again with all accounts for sign-up flow
+ Log.d("GoogleAuthProvider", "No authorized accounts found, showing all Google accounts for sign-up")
+ try {
+ (testCredentialManagerProvider ?: credentialManagerProvider).getGoogleCredential(
+ context = context,
+ credentialManager = CredentialManager.create(context),
+ serverClientId = provider.serverClientId!!,
+ filterByAuthorizedAccounts = false,
+ autoSelectEnabled = provider.autoSelectEnabled
+ )
+ } catch (fallbackException: NoCredentialException) {
+ // No Google accounts available on device at all
+ throw AuthException.UnknownException(
+ message = "No Google accounts available.\n\nPlease add a Google account to your device and try again.",
+ cause = fallbackException
+ )
+ }
+ }
+ } else {
+ // Developer explicitly wants to show all accounts (no fallback needed)
(testCredentialManagerProvider ?: credentialManagerProvider).getGoogleCredential(
context = context,
credentialManager = CredentialManager.create(context),
serverClientId = provider.serverClientId!!,
- filterByAuthorizedAccounts = true,
- autoSelectEnabled = false
+ filterByAuthorizedAccounts = false,
+ autoSelectEnabled = provider.autoSelectEnabled
)
+ }
idTokenFromResult = result.idToken
signInAndLinkWithCredential(
diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/AuthUIConfigurationTest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/AuthUIConfigurationTest.kt
index 80cf85ad6..4afcfa84b 100644
--- a/auth/src/test/java/com/firebase/ui/auth/configuration/AuthUIConfigurationTest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/configuration/AuthUIConfigurationTest.kt
@@ -294,12 +294,13 @@ class AuthUIConfigurationTest {
}
@Test
+ @Config(manifest = Config.NONE, qualifiers = "night")
fun `validation accepts all supported providers`() {
val config = authUIConfiguration {
context = applicationContext
providers {
provider(AuthProvider.Google(scopes = listOf(), serverClientId = "test_client_id"))
- provider(AuthProvider.Facebook(applicationId = "test_app_id"))
+ provider(AuthProvider.Facebook())
provider(AuthProvider.Twitter(customParameters = mapOf()))
provider(AuthProvider.Github(customParameters = mapOf()))
provider(AuthProvider.Microsoft(customParameters = mapOf(), tenant = null))
diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/AuthProviderTest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/AuthProviderTest.kt
index f64bfc2a6..718d38ad3 100644
--- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/AuthProviderTest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/AuthProviderTest.kt
@@ -2,6 +2,7 @@ package com.firebase.ui.auth.configuration.auth_provider
import android.content.Context
import androidx.test.core.app.ApplicationProvider
+import com.firebase.ui.auth.R
import com.google.common.truth.Truth.assertThat
import com.google.firebase.auth.actionCodeSettings
import org.junit.Before
@@ -264,32 +265,34 @@ class AuthProviderTest {
}
}
+ @Test
+ @Config(manifest = Config.NONE, qualifiers = "night")
+ fun `google provider assigns default_web_client_id to serverClientId when null`() {
+ val provider = AuthProvider.Google(
+ scopes = listOf("email"),
+ serverClientId = null
+ )
+
+ provider.validate(applicationContext)
+
+ assertThat(provider.serverClientId)
+ .isEqualTo(applicationContext.getString(R.string.default_web_client_id))
+ }
+
// =============================================================================================
// Facebook Provider Tests
// =============================================================================================
@Test
+ @Config(manifest = Config.NONE, qualifiers = "night")
fun `facebook provider with valid configuration should succeed`() {
- val provider = AuthProvider.Facebook(applicationId = "application_id")
+ val provider = AuthProvider.Facebook()
provider.validate(applicationContext)
}
@Test
- fun `facebook provider with empty application id throws`() {
- val provider = AuthProvider.Facebook(applicationId = "")
-
- try {
- provider.validate(applicationContext)
- assertThat(false).isTrue() // Should not reach here
- } catch (e: Exception) {
- assertThat(e).isInstanceOf(IllegalArgumentException::class.java)
- assertThat(e.message).isEqualTo("Facebook application ID cannot be blank")
- }
- }
-
- @Test
- fun `facebook provider validates facebook_application_id when applicationId is null`() {
+ fun `facebook provider validates facebook_application_id`() {
val provider = AuthProvider.Facebook()
try {
@@ -299,7 +302,7 @@ class AuthProviderTest {
assertThat(e).isInstanceOf(IllegalStateException::class.java)
assertThat(e.message).isEqualTo(
"Facebook provider unconfigured. Make sure to " +
- "add a `facebook_application_id` string or provide applicationId parameter."
+ "add a `facebook_application_id` string to your strings.xml"
)
}
}
@@ -400,4 +403,4 @@ class AuthProviderTest {
assertThat(e.message).isEqualTo("Button label cannot be null or empty")
}
}
-}
\ No newline at end of file
+}
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 155de1f82..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
@@ -103,6 +103,7 @@ class FacebookAuthProviderFirebaseAuthUITest {
}
@Test
+ @Config(manifest = Config.NONE, qualifiers = "night")
fun `signInWithFacebook - successful sign in signs user in and emits Success authState`() = runTest {
val authStateListeners = mutableListOf()
doAnswer { invocation ->
@@ -118,9 +119,7 @@ class FacebookAuthProviderFirebaseAuthUITest {
whenever(mockFirebaseAuth.currentUser).thenReturn(null)
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
- val provider = spy(AuthProvider.Facebook(
- applicationId = "000000000000"
- ))
+ val provider = spy(AuthProvider.Facebook())
val config = authUIConfiguration {
context = applicationContext
providers {
@@ -175,6 +174,7 @@ class FacebookAuthProviderFirebaseAuthUITest {
}
@Test
+ @Config(manifest = Config.NONE, qualifiers = "night")
fun `signInWithFacebook - handles account collision by saving credential and emitting error`() = runTest {
EmailLinkPersistenceManager.default.clear(applicationContext)
EmailLinkPersistenceManager.default.saveEmail(
@@ -185,9 +185,7 @@ class FacebookAuthProviderFirebaseAuthUITest {
)
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
- val provider = spy(AuthProvider.Facebook(
- applicationId = "000000000000"
- ))
+ val provider = spy(AuthProvider.Facebook())
val config = authUIConfiguration {
context = applicationContext
providers {
@@ -238,11 +236,10 @@ class FacebookAuthProviderFirebaseAuthUITest {
}
@Test
+ @Config(manifest = Config.NONE, qualifiers = "night")
fun `signInWithFacebook - converts FacebookException into AuthException`() = runTest {
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
- val provider = spy(AuthProvider.Facebook(
- applicationId = "000000000000"
- ))
+ val provider = spy(AuthProvider.Facebook())
val config = authUIConfiguration {
context = applicationContext
providers {
diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt
index 9091e7d7c..2fd855c37 100644
--- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt
@@ -602,6 +602,200 @@ class GoogleAuthProviderFirebaseAuthUITest {
verify(mockFirebaseAuth, never()).signInWithCredential(any())
}
+ // =============================================================================================
+ // signInWithGoogle - Configuration Properties
+ // =============================================================================================
+
+ @Test
+ fun `Sign in with Google with default settings passes filterByAuthorizedAccounts=true`() = runTest {
+ val mockCredential = mock(AuthCredential::class.java)
+ val mockUser = mock(FirebaseUser::class.java)
+ val mockAuthResult = mock(AuthResult::class.java)
+ `when`(mockAuthResult.user).thenReturn(mockUser)
+
+ val googleSignInResult = AuthProvider.Google.GoogleSignInResult(
+ credential = mockCredential,
+ idToken = "test-id-token",
+ displayName = "Test User",
+ photoUrl = null
+ )
+
+ `when`(
+ mockCredentialManagerProvider.getGoogleCredential(
+ context = eq(applicationContext),
+ credentialManager = any(),
+ serverClientId = eq("test-client-id"),
+ filterByAuthorizedAccounts = eq(true),
+ autoSelectEnabled = eq(false)
+ )
+ ).thenReturn(googleSignInResult)
+
+ val taskCompletionSource = TaskCompletionSource()
+ taskCompletionSource.setResult(mockAuthResult)
+ `when`(mockFirebaseAuth.signInWithCredential(mockCredential))
+ .thenReturn(taskCompletionSource.task)
+ `when`(mockFirebaseAuth.currentUser).thenReturn(null)
+
+ val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
+ val googleProvider = AuthProvider.Google(
+ serverClientId = "test-client-id",
+ scopes = emptyList()
+ // filterByAuthorizedAccounts defaults to true
+ // autoSelectEnabled defaults to false
+ )
+ val config = authUIConfiguration {
+ context = applicationContext
+ providers {
+ provider(googleProvider)
+ }
+ }
+
+ instance.signInWithGoogle(
+ context = applicationContext,
+ config = config,
+ provider = googleProvider,
+ authorizationProvider = mockAuthorizationProvider,
+ credentialManagerProvider = mockCredentialManagerProvider
+ )
+
+ // Verify correct parameters were passed
+ verify(mockCredentialManagerProvider).getGoogleCredential(
+ context = eq(applicationContext),
+ credentialManager = any(),
+ serverClientId = eq("test-client-id"),
+ filterByAuthorizedAccounts = eq(true),
+ autoSelectEnabled = eq(false)
+ )
+
+ verify(mockFirebaseAuth).signInWithCredential(mockCredential)
+ }
+
+ @Test
+ fun `Sign in with Google with filterByAuthorizedAccounts=false passes correct parameter`() = runTest {
+ val mockCredential = mock(AuthCredential::class.java)
+ val mockUser = mock(FirebaseUser::class.java)
+ val mockAuthResult = mock(AuthResult::class.java)
+ `when`(mockAuthResult.user).thenReturn(mockUser)
+
+ val googleSignInResult = AuthProvider.Google.GoogleSignInResult(
+ credential = mockCredential,
+ idToken = "test-id-token",
+ displayName = "Test User",
+ photoUrl = null
+ )
+
+ `when`(
+ mockCredentialManagerProvider.getGoogleCredential(
+ context = eq(applicationContext),
+ credentialManager = any(),
+ serverClientId = eq("test-client-id"),
+ filterByAuthorizedAccounts = eq(false),
+ autoSelectEnabled = eq(false)
+ )
+ ).thenReturn(googleSignInResult)
+
+ val taskCompletionSource = TaskCompletionSource()
+ taskCompletionSource.setResult(mockAuthResult)
+ `when`(mockFirebaseAuth.signInWithCredential(mockCredential))
+ .thenReturn(taskCompletionSource.task)
+ `when`(mockFirebaseAuth.currentUser).thenReturn(null)
+
+ val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
+ val googleProvider = AuthProvider.Google(
+ serverClientId = "test-client-id",
+ scopes = emptyList(),
+ filterByAuthorizedAccounts = false
+ )
+ val config = authUIConfiguration {
+ context = applicationContext
+ providers {
+ provider(googleProvider)
+ }
+ }
+
+ instance.signInWithGoogle(
+ context = applicationContext,
+ config = config,
+ provider = googleProvider,
+ authorizationProvider = mockAuthorizationProvider,
+ credentialManagerProvider = mockCredentialManagerProvider
+ )
+
+ // Verify filterByAuthorizedAccounts=false was passed
+ verify(mockCredentialManagerProvider).getGoogleCredential(
+ context = eq(applicationContext),
+ credentialManager = any(),
+ serverClientId = eq("test-client-id"),
+ filterByAuthorizedAccounts = eq(false),
+ autoSelectEnabled = eq(false)
+ )
+
+ verify(mockFirebaseAuth).signInWithCredential(mockCredential)
+ }
+
+ @Test
+ fun `Sign in with Google with autoSelectEnabled=true passes correct parameter`() = runTest {
+ val mockCredential = mock(AuthCredential::class.java)
+ val mockUser = mock(FirebaseUser::class.java)
+ val mockAuthResult = mock(AuthResult::class.java)
+ `when`(mockAuthResult.user).thenReturn(mockUser)
+
+ val googleSignInResult = AuthProvider.Google.GoogleSignInResult(
+ credential = mockCredential,
+ idToken = "test-id-token",
+ displayName = "Test User",
+ photoUrl = null
+ )
+
+ `when`(
+ mockCredentialManagerProvider.getGoogleCredential(
+ context = eq(applicationContext),
+ credentialManager = any(),
+ serverClientId = eq("test-client-id"),
+ filterByAuthorizedAccounts = eq(true),
+ autoSelectEnabled = eq(true)
+ )
+ ).thenReturn(googleSignInResult)
+
+ val taskCompletionSource = TaskCompletionSource()
+ taskCompletionSource.setResult(mockAuthResult)
+ `when`(mockFirebaseAuth.signInWithCredential(mockCredential))
+ .thenReturn(taskCompletionSource.task)
+ `when`(mockFirebaseAuth.currentUser).thenReturn(null)
+
+ val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
+ val googleProvider = AuthProvider.Google(
+ serverClientId = "test-client-id",
+ scopes = emptyList(),
+ autoSelectEnabled = true
+ )
+ val config = authUIConfiguration {
+ context = applicationContext
+ providers {
+ provider(googleProvider)
+ }
+ }
+
+ instance.signInWithGoogle(
+ context = applicationContext,
+ config = config,
+ provider = googleProvider,
+ authorizationProvider = mockAuthorizationProvider,
+ credentialManagerProvider = mockCredentialManagerProvider
+ )
+
+ // Verify autoSelectEnabled=true was passed
+ verify(mockCredentialManagerProvider).getGoogleCredential(
+ context = eq(applicationContext),
+ credentialManager = any(),
+ serverClientId = eq("test-client-id"),
+ filterByAuthorizedAccounts = eq(true),
+ autoSelectEnabled = eq(true)
+ )
+
+ verify(mockFirebaseAuth).signInWithCredential(mockCredential)
+ }
+
// =============================================================================================
// signInWithGoogle - State Management
// =============================================================================================
diff --git a/auth/src/test/res/values-night/config.xml b/auth/src/test/res/values-night/config.xml
new file mode 100644
index 000000000..0b31fe015
--- /dev/null
+++ b/auth/src/test/res/values-night/config.xml
@@ -0,0 +1,7 @@
+
+
+ test_client_id
+ test_app_id
+ test_login_scheme
+ test_client_token
+