Skip to content

Commit aceafd4

Browse files
committed
fix: fallback to all Google accounts if no authorized accounts found during sign-in
- Add filterByAuthorizedAccounts and autoSelectEnabled properties to AuthProvider.Google
1 parent 5f7339a commit aceafd4

File tree

4 files changed

+246
-4
lines changed

4 files changed

+246
-4
lines changed

app/src/main/java/com/firebaseui/android/demo/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import com.google.firebase.FirebaseApp
3636
*/
3737
class MainActivity : ComponentActivity() {
3838
companion object {
39-
private const val USE_AUTH_EMULATOR = true
39+
private const val USE_AUTH_EMULATOR = false
4040
private const val AUTH_EMULATOR_HOST = "10.0.2.2"
4141
private const val AUTH_EMULATOR_PORT = 9099
4242
}

auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AuthProvider.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,20 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
487487
*/
488488
var serverClientId: String?,
489489

490+
/**
491+
* Whether to filter by authorized accounts.
492+
* When true, only shows Google accounts that have previously authorized this app.
493+
* Defaults to true, with automatic fallback to false if no authorized accounts found.
494+
*/
495+
val filterByAuthorizedAccounts: Boolean = true,
496+
497+
/**
498+
* Whether to enable auto-select for single account scenarios.
499+
* When true, automatically selects the account if only one is available.
500+
* Defaults to false for better user control.
501+
*/
502+
val autoSelectEnabled: Boolean = false,
503+
490504
/**
491505
* A map of custom OAuth parameters.
492506
*/

auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,48 @@ internal suspend fun FirebaseAuthUI.signInWithGoogle(
133133
}
134134
}
135135

136-
val result =
136+
// Try with configured filterByAuthorizedAccounts setting
137+
// If default (true), fallback to false if no authorized accounts found
138+
// See: https://developer.android.com/identity/sign-in/credential-manager-siwg#siwg-button
139+
val result = if (provider.filterByAuthorizedAccounts) {
140+
// Default behavior: Try authorized accounts first, fallback to all accounts
141+
try {
142+
(testCredentialManagerProvider ?: credentialManagerProvider).getGoogleCredential(
143+
context = context,
144+
credentialManager = CredentialManager.create(context),
145+
serverClientId = provider.serverClientId!!,
146+
filterByAuthorizedAccounts = true,
147+
autoSelectEnabled = provider.autoSelectEnabled
148+
)
149+
} catch (e: NoCredentialException) {
150+
// No authorized accounts found, try again with all accounts for sign-up flow
151+
Log.d("GoogleAuthProvider", "No authorized accounts found, showing all Google accounts for sign-up")
152+
try {
153+
(testCredentialManagerProvider ?: credentialManagerProvider).getGoogleCredential(
154+
context = context,
155+
credentialManager = CredentialManager.create(context),
156+
serverClientId = provider.serverClientId!!,
157+
filterByAuthorizedAccounts = false,
158+
autoSelectEnabled = provider.autoSelectEnabled
159+
)
160+
} catch (fallbackException: NoCredentialException) {
161+
// No Google accounts available on device at all
162+
throw AuthException.UnknownException(
163+
message = "No Google accounts available.\n\nPlease add a Google account to your device and try again.",
164+
cause = fallbackException
165+
)
166+
}
167+
}
168+
} else {
169+
// Developer explicitly wants to show all accounts (no fallback needed)
137170
(testCredentialManagerProvider ?: credentialManagerProvider).getGoogleCredential(
138171
context = context,
139172
credentialManager = CredentialManager.create(context),
140173
serverClientId = provider.serverClientId!!,
141-
filterByAuthorizedAccounts = true,
142-
autoSelectEnabled = false
174+
filterByAuthorizedAccounts = false,
175+
autoSelectEnabled = provider.autoSelectEnabled
143176
)
177+
}
144178
idTokenFromResult = result.idToken
145179

146180
signInAndLinkWithCredential(

auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,200 @@ class GoogleAuthProviderFirebaseAuthUITest {
602602
verify(mockFirebaseAuth, never()).signInWithCredential(any())
603603
}
604604

605+
// =============================================================================================
606+
// signInWithGoogle - Configuration Properties
607+
// =============================================================================================
608+
609+
@Test
610+
fun `Sign in with Google with default settings passes filterByAuthorizedAccounts=true`() = runTest {
611+
val mockCredential = mock(AuthCredential::class.java)
612+
val mockUser = mock(FirebaseUser::class.java)
613+
val mockAuthResult = mock(AuthResult::class.java)
614+
`when`(mockAuthResult.user).thenReturn(mockUser)
615+
616+
val googleSignInResult = AuthProvider.Google.GoogleSignInResult(
617+
credential = mockCredential,
618+
idToken = "test-id-token",
619+
displayName = "Test User",
620+
photoUrl = null
621+
)
622+
623+
`when`(
624+
mockCredentialManagerProvider.getGoogleCredential(
625+
context = eq(applicationContext),
626+
credentialManager = any<CredentialManager>(),
627+
serverClientId = eq("test-client-id"),
628+
filterByAuthorizedAccounts = eq(true),
629+
autoSelectEnabled = eq(false)
630+
)
631+
).thenReturn(googleSignInResult)
632+
633+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
634+
taskCompletionSource.setResult(mockAuthResult)
635+
`when`(mockFirebaseAuth.signInWithCredential(mockCredential))
636+
.thenReturn(taskCompletionSource.task)
637+
`when`(mockFirebaseAuth.currentUser).thenReturn(null)
638+
639+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
640+
val googleProvider = AuthProvider.Google(
641+
serverClientId = "test-client-id",
642+
scopes = emptyList()
643+
// filterByAuthorizedAccounts defaults to true
644+
// autoSelectEnabled defaults to false
645+
)
646+
val config = authUIConfiguration {
647+
context = applicationContext
648+
providers {
649+
provider(googleProvider)
650+
}
651+
}
652+
653+
instance.signInWithGoogle(
654+
context = applicationContext,
655+
config = config,
656+
provider = googleProvider,
657+
authorizationProvider = mockAuthorizationProvider,
658+
credentialManagerProvider = mockCredentialManagerProvider
659+
)
660+
661+
// Verify correct parameters were passed
662+
verify(mockCredentialManagerProvider).getGoogleCredential(
663+
context = eq(applicationContext),
664+
credentialManager = any<CredentialManager>(),
665+
serverClientId = eq("test-client-id"),
666+
filterByAuthorizedAccounts = eq(true),
667+
autoSelectEnabled = eq(false)
668+
)
669+
670+
verify(mockFirebaseAuth).signInWithCredential(mockCredential)
671+
}
672+
673+
@Test
674+
fun `Sign in with Google with filterByAuthorizedAccounts=false passes correct parameter`() = runTest {
675+
val mockCredential = mock(AuthCredential::class.java)
676+
val mockUser = mock(FirebaseUser::class.java)
677+
val mockAuthResult = mock(AuthResult::class.java)
678+
`when`(mockAuthResult.user).thenReturn(mockUser)
679+
680+
val googleSignInResult = AuthProvider.Google.GoogleSignInResult(
681+
credential = mockCredential,
682+
idToken = "test-id-token",
683+
displayName = "Test User",
684+
photoUrl = null
685+
)
686+
687+
`when`(
688+
mockCredentialManagerProvider.getGoogleCredential(
689+
context = eq(applicationContext),
690+
credentialManager = any<CredentialManager>(),
691+
serverClientId = eq("test-client-id"),
692+
filterByAuthorizedAccounts = eq(false),
693+
autoSelectEnabled = eq(false)
694+
)
695+
).thenReturn(googleSignInResult)
696+
697+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
698+
taskCompletionSource.setResult(mockAuthResult)
699+
`when`(mockFirebaseAuth.signInWithCredential(mockCredential))
700+
.thenReturn(taskCompletionSource.task)
701+
`when`(mockFirebaseAuth.currentUser).thenReturn(null)
702+
703+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
704+
val googleProvider = AuthProvider.Google(
705+
serverClientId = "test-client-id",
706+
scopes = emptyList(),
707+
filterByAuthorizedAccounts = false
708+
)
709+
val config = authUIConfiguration {
710+
context = applicationContext
711+
providers {
712+
provider(googleProvider)
713+
}
714+
}
715+
716+
instance.signInWithGoogle(
717+
context = applicationContext,
718+
config = config,
719+
provider = googleProvider,
720+
authorizationProvider = mockAuthorizationProvider,
721+
credentialManagerProvider = mockCredentialManagerProvider
722+
)
723+
724+
// Verify filterByAuthorizedAccounts=false was passed
725+
verify(mockCredentialManagerProvider).getGoogleCredential(
726+
context = eq(applicationContext),
727+
credentialManager = any<CredentialManager>(),
728+
serverClientId = eq("test-client-id"),
729+
filterByAuthorizedAccounts = eq(false),
730+
autoSelectEnabled = eq(false)
731+
)
732+
733+
verify(mockFirebaseAuth).signInWithCredential(mockCredential)
734+
}
735+
736+
@Test
737+
fun `Sign in with Google with autoSelectEnabled=true passes correct parameter`() = runTest {
738+
val mockCredential = mock(AuthCredential::class.java)
739+
val mockUser = mock(FirebaseUser::class.java)
740+
val mockAuthResult = mock(AuthResult::class.java)
741+
`when`(mockAuthResult.user).thenReturn(mockUser)
742+
743+
val googleSignInResult = AuthProvider.Google.GoogleSignInResult(
744+
credential = mockCredential,
745+
idToken = "test-id-token",
746+
displayName = "Test User",
747+
photoUrl = null
748+
)
749+
750+
`when`(
751+
mockCredentialManagerProvider.getGoogleCredential(
752+
context = eq(applicationContext),
753+
credentialManager = any<CredentialManager>(),
754+
serverClientId = eq("test-client-id"),
755+
filterByAuthorizedAccounts = eq(true),
756+
autoSelectEnabled = eq(true)
757+
)
758+
).thenReturn(googleSignInResult)
759+
760+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
761+
taskCompletionSource.setResult(mockAuthResult)
762+
`when`(mockFirebaseAuth.signInWithCredential(mockCredential))
763+
.thenReturn(taskCompletionSource.task)
764+
`when`(mockFirebaseAuth.currentUser).thenReturn(null)
765+
766+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
767+
val googleProvider = AuthProvider.Google(
768+
serverClientId = "test-client-id",
769+
scopes = emptyList(),
770+
autoSelectEnabled = true
771+
)
772+
val config = authUIConfiguration {
773+
context = applicationContext
774+
providers {
775+
provider(googleProvider)
776+
}
777+
}
778+
779+
instance.signInWithGoogle(
780+
context = applicationContext,
781+
config = config,
782+
provider = googleProvider,
783+
authorizationProvider = mockAuthorizationProvider,
784+
credentialManagerProvider = mockCredentialManagerProvider
785+
)
786+
787+
// Verify autoSelectEnabled=true was passed
788+
verify(mockCredentialManagerProvider).getGoogleCredential(
789+
context = eq(applicationContext),
790+
credentialManager = any<CredentialManager>(),
791+
serverClientId = eq("test-client-id"),
792+
filterByAuthorizedAccounts = eq(true),
793+
autoSelectEnabled = eq(true)
794+
)
795+
796+
verify(mockFirebaseAuth).signInWithCredential(mockCredential)
797+
}
798+
605799
// =============================================================================================
606800
// signInWithGoogle - State Management
607801
// =============================================================================================

0 commit comments

Comments
 (0)