FirebaseUI Auth is a modern, Compose-based authentication library that provides drop-in UI components for Firebase Authentication. It eliminates boilerplate code and promotes best practices for user authentication on Android.
Built entirely with Jetpack Compose and Material Design 3, FirebaseUI Auth offers:
- Simple API - Choose between high-level screens or low-level controllers for maximum flexibility
- 12+ Authentication Methods - Email/Password, Phone, Google, Facebook, Twitter, GitHub, Microsoft, Yahoo, Apple, Anonymous, and custom OAuth providers
- Multi-Factor Authentication - SMS and TOTP (Time-based One-Time Password) with recovery codes
- Android Credential Manager - Automatic credential saving and one-tap sign-in
- Material Design 3 - Beautiful, themeable UI components that integrate seamlessly with your app
- Localization Support - Customizable strings for internationalization
- Security Best Practices - Email verification, reauthentication, account linking, and more
Equivalent FirebaseUI libraries are available for iOS and Web.
Ensure your application is configured for use with Firebase. See the Firebase documentation for setup instructions.
Minimum Requirements:
- Android SDK 21+ (Android 5.0 Lollipop)
- Kotlin 1.9+
- Jetpack Compose (Compiler 1.5+)
- Firebase Auth 22.0.0+
Add the FirebaseUI Auth library dependency to your build.gradle.kts (Module):
dependencies {
// FirebaseUI for Auth
implementation("com.firebaseui:firebase-ui-auth:10.0.0-beta02")
// Required: Firebase Auth
implementation(platform("com.google.firebase:firebase-bom:32.7.0"))
implementation("com.google.firebase:firebase-auth")
// Required: Jetpack Compose
implementation(platform("androidx.compose:compose-bom:2024.01.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
// Optional: Facebook Login (if using FacebookAuthProvider)
implementation("com.facebook.android:facebook-login:16.3.0")
}Localization Support:
To optimize APK size, configure resource filtering for only the languages your app supports:
android {
defaultConfig {
resourceConfigurations += listOf("en", "es", "fr") // Add your supported languages
}
}Google Sign-In configuration is automatically provided by the google-services Gradle plugin. Ensure you have enabled Google Sign-In in the Firebase Console.
If using Facebook Login, add your Facebook App ID to strings.xml:
<resources>
<string name="facebook_application_id" translatable="false">YOUR_FACEBOOK_APP_ID</string>
<string name="facebook_login_protocol_scheme" translatable="false">fbYOUR_FACEBOOK_APP_ID</string>
<string name="facebook_client_token" translatable="false">CHANGE-ME</string>
</resources>See the Facebook for Developers documentation for setup instructions.
Twitter, GitHub, Microsoft, Yahoo, and Apple providers require configuration in the Firebase Console but no additional Android-specific setup. See the Firebase Auth documentation for provider-specific instructions.
Here's the simplest way to add authentication to your app with Email and Google Sign-In:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
provider(AuthProvider.Google())
}
}
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { result ->
Toast.makeText(this, "Welcome!", Toast.LENGTH_SHORT).show()
// Navigate to main app screen
},
onSignInFailure = { exception ->
Toast.makeText(this, "Error: ${exception.message}", Toast.LENGTH_SHORT).show()
},
onSignInCancelled = {
finish()
}
)
}
}
}
}That's it! This provides a complete authentication flow with:
- ✅ Email/password sign-in and sign-up
- ✅ Google Sign-In
- ✅ Password reset
- ✅ Display name collection
- ✅ Credential Manager integration
- ✅ Material Design 3 theming
- ✅ Error handling
Before showing the authentication UI, check if a user is already signed in:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val authUI = FirebaseAuthUI.getInstance()
if (authUI.isSignedIn()) {
// User is already signed in, navigate to main app
startActivity(Intent(this, MainAppActivity::class.java))
finish()
} else {
// Show authentication UI
setContent {
FirebaseAuthScreen(/* ... */)
}
}
}
}Or observe authentication state changes reactively:
@Composable
fun AuthGate() {
val authUI = remember { FirebaseAuthUI.getInstance() }
val authState by authUI.authStateFlow().collectAsState(initial = AuthState.Idle)
when {
authState is AuthState.Success -> {
// User is signed in
MainAppScreen()
}
else -> {
// Show authentication
FirebaseAuthScreen(/* ... */)
}
}
}FirebaseAuthUI is the central class that coordinates all authentication operations. It manages UI state and provides methods for signing in, signing up, and managing user accounts.
// Get the default instance
val authUI = FirebaseAuthUI.getInstance()
// Or get an instance for a specific Firebase app
val customApp = Firebase.app("secondary")
val authUI = FirebaseAuthUI.getInstance(customApp)
// Or create with custom auth (for multi-tenancy)
val customAuth = Firebase.auth(customApp)
val authUI = FirebaseAuthUI.create(auth = customAuth)Key Methods:
| Method | Return Type | Description |
|---|---|---|
isSignedIn() |
Boolean |
Checks if a user is currently signed in |
getCurrentUser() |
FirebaseUser? |
Returns the current user, if signed in |
authStateFlow() |
Flow<AuthState> |
Observes authentication state changes |
createAuthFlow(config) |
AuthFlowController |
Creates a sign-in flow controller |
signOut(context) |
suspend fun |
Signs out the current user |
delete(context) |
suspend fun |
Deletes the current user account |
AuthUIConfiguration defines all settings for your authentication flow. Use the DSL builder function for easy configuration:
val configuration = authUIConfiguration {
// Required: Authentication providers
providers {
provider(AuthProvider.Email())
provider(AuthProvider.Google())
provider(AuthProvider.Phone())
}
// Optional: Theme configuration
theme = AuthUITheme.fromMaterialTheme()
// Optional: Terms of Service and Privacy Policy URLs
tosUrl = "https://example.com/terms"
privacyPolicyUrl = "https://example.com/privacy"
// Optional: App logo
logo = Icons.Default.AccountCircle
// Optional: Enable MFA (default: true)
isMfaEnabled = true
// Optional: Enable Credential Manager (default: true)
isCredentialManagerEnabled = true
// Optional: Allow anonymous user upgrade (default: false)
isAnonymousUpgradeEnabled = true
// Optional: Require display name on sign-up (default: true)
isDisplayNameRequired = true
// Optional: Allow new email accounts (default: true)
isNewEmailAccountsAllowed = true
// Optional: Always show provider choice even with one provider (default: false)
isProviderChoiceAlwaysShown = false
// Optional: Custom string provider for localization
stringProvider = MyCustomStringProvider()
// Optional: Locale override
locale = Locale.FRENCH
}AuthFlowController manages the lifecycle of an authentication flow programmatically. This is the low-level API for advanced use cases.
val controller = authUI.createAuthFlow(configuration)
lifecycleScope.launch {
// Start the flow
val state = controller.start()
when (state) {
is AuthState.Success -> {
// Handle success
val user = state.result.user
}
is AuthState.Error -> {
// Handle error
Log.e(TAG, "Auth failed", state.exception)
}
is AuthState.Cancelled -> {
// User cancelled
}
else -> {
// Handle other states (RequiresMfa, RequiresEmailVerification, etc.)
}
}
}
// Cancel the flow if needed
controller.cancel()
// Clean up when done
override fun onDestroy() {
super.onDestroy()
controller.dispose()
}AuthState represents the current state of authentication:
sealed class AuthState {
object Idle : AuthState()
data class Loading(val message: String?) : AuthState()
data class Success(val result: AuthResult?, val user: FirebaseUser, val isNewUser: Boolean = false) : AuthState()
data class Error(val exception: AuthException, val isRecoverable: Boolean) : AuthState()
data class RequiresMfa(val resolver: MultiFactorResolver, val hint: String? = null) : AuthState()
data class RequiresEmailVerification(val user: FirebaseUser, val email: String) : AuthState()
data class RequiresProfileCompletion(val user: FirebaseUser, val missingFields: List<String> = emptyList()) : AuthState()
object Cancelled : AuthState()
object PasswordResetLinkSent : AuthState()
object EmailSignInLinkSent : AuthState()
data class SMSAutoVerified(val credential: PhoneAuthCredential) : AuthState()
data class PhoneNumberVerificationRequired(
val verificationId: String,
val forceResendingToken: PhoneAuthProvider.ForceResendingToken
) : AuthState()
}Configure email/password authentication with optional customization:
val emailProvider = AuthProvider.Email(
// Optional: Require display name (default: true)
isDisplayNameRequired = true,
// Optional: Enable email link sign-in (default: false)
isEmailLinkSignInEnabled = true,
// Optional: Force email link on same device (default: true)
isEmailLinkForceSameDeviceEnabled = true,
// Optional: Action code settings for email link
emailLinkActionCodeSettings = actionCodeSettings {
url = "https://example.com/auth"
handleCodeInApp = true
setAndroidPackageName(packageName, true, null)
},
// Optional: Allow new accounts (default: true)
isNewAccountsAllowed = true,
// Optional: Minimum password length (default: 6)
minimumPasswordLength = 8,
// Optional: Custom password validation rules
passwordValidationRules = listOf(
PasswordRule.MinimumLength(8),
PasswordRule.RequireUppercase,
PasswordRule.RequireLowercase,
PasswordRule.RequireDigit,
PasswordRule.RequireSpecialCharacter
)
)
val configuration = authUIConfiguration {
providers = listOf(emailProvider)
}Configure phone number authentication with SMS verification:
val phoneProvider = AuthProvider.Phone(
// Optional: Default phone number in international format
defaultNumber = "+15551234567",
// Optional: Default country code (ISO alpha-2 format)
defaultCountryCode = "US",
// Optional: Allowed countries
allowedCountries = listOf("US", "CA", "GB"),
// Optional: SMS code length (default: 6)
smsCodeLength = 6,
// Optional: Timeout for SMS delivery in seconds (default: 60)
timeout = 60L,
// Optional: Enable instant verification (default: true)
isInstantVerificationEnabled = true
)
val configuration = authUIConfiguration {
providers {
provider(phoneProvider)
}
}Configure Google Sign-In with optional scopes and server client ID:
val googleProvider = AuthProvider.Google(
// Required: Scopes to request
scopes = listOf("https://www.googleapis.com/auth/drive.file"),
// Optional: Server client ID for backend authentication
serverClientId = "YOUR_SERVER_CLIENT_ID.apps.googleusercontent.com",
// Optional: Custom OAuth parameters
customParameters = mapOf("prompt" to "select_account")
)
val configuration = authUIConfiguration {
providers {
provider(googleProvider)
}
}Configure Facebook Login with optional permissions:
val facebookProvider = AuthProvider.Facebook(
// Optional: Permissions to request (default: ["email", "public_profile"])
scopes = listOf("email", "public_profile", "user_friends"),
// Optional: Custom OAuth parameters
customParameters = mapOf("display" to "popup")
)
val configuration = authUIConfiguration {
providers {
provider(facebookProvider)
}
}FirebaseUI supports Twitter, GitHub, Microsoft, Yahoo, and Apple:
// Twitter
val twitterProvider = AuthProvider.Twitter(
// Required: Custom OAuth parameters
customParameters = mapOf("lang" to "en")
)
// GitHub
val githubProvider = AuthProvider.Github(
// Optional: Scopes to request (default: ["user:email"])
scopes = listOf("user:email", "read:user"),
// Required: Custom OAuth parameters
customParameters = mapOf("allow_signup" to "false")
)
// Microsoft
val microsoftProvider = AuthProvider.Microsoft(
// Optional: Scopes to request (default: ["openid", "profile", "email"])
scopes = listOf("openid", "profile", "email", "User.Read"),
// Optional: Tenant ID for Azure Active Directory
tenant = "YOUR_TENANT_ID",
// Required: Custom OAuth parameters
customParameters = mapOf("prompt" to "consent")
)
// Yahoo
val yahooProvider = AuthProvider.Yahoo(
// Optional: Scopes to request (default: ["openid", "profile", "email"])
scopes = listOf("openid", "profile", "email"),
// Required: Custom OAuth parameters
customParameters = mapOf("language" to "en-us")
)
// Apple
val appleProvider = AuthProvider.Apple(
// Optional: Scopes to request (default: ["name", "email"])
scopes = listOf("name", "email"),
// Optional: Locale for the sign-in page
locale = "en_US",
// Required: Custom OAuth parameters
customParameters = mapOf("ui_locales" to "en-US")
)
val configuration = authUIConfiguration {
providers {
provider(twitterProvider)
provider(githubProvider)
provider(microsoftProvider)
provider(yahooProvider)
provider(appleProvider)
}
}Enable anonymous authentication to let users use your app without signing in:
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Anonymous())
}
// Enable anonymous user upgrade
isAnonymousUpgradeEnabled = true
}Support any OAuth provider configured in the Firebase Console:
val lineProvider = AuthProvider.GenericOAuth(
// Required: Provider name
providerName = "LINE",
// Required: Provider ID as configured in Firebase Console
providerId = "oidc.line",
// Required: Scopes to request
scopes = listOf("profile", "openid", "email"),
// Required: Custom OAuth parameters
customParameters = mapOf("prompt" to "consent"),
// Required: Button label
buttonLabel = "Sign in with LINE",
// Optional: Custom button icon
buttonIcon = AuthUIAsset.Resource(R.drawable.ic_line),
// Optional: Custom button background color
buttonColor = Color(0xFF06C755),
// Optional: Custom button content color
contentColor = Color.White
)
val configuration = authUIConfiguration {
providers {
provider(lineProvider)
}
}The high-level API provides a complete, opinionated authentication experience with minimal code:
@Composable
fun AuthenticationScreen() {
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
provider(AuthProvider.Google())
provider(AuthProvider.Facebook())
provider(AuthProvider.Phone())
}
tosUrl = "https://example.com/terms"
privacyPolicyUrl = "https://example.com/privacy"
logo = Icons.Default.Lock
}
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { result ->
val user = result.user
val isNewUser = result.additionalUserInfo?.isNewUser ?: false
if (isNewUser) {
// First-time user
navigateToOnboarding()
} else {
// Returning user
navigateToHome()
}
},
onSignInFailure = { exception ->
when (exception) {
is AuthException.NetworkException -> {
showSnackbar("No internet connection")
}
is AuthException.TooManyRequestsException -> {
showSnackbar("Too many attempts. Please try again later.")
}
else -> {
showSnackbar("Authentication failed: ${exception.message}")
}
}
},
onSignInCancelled = {
navigateBack()
}
)
}FirebaseAuthScreen Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
configuration |
AuthUIConfiguration |
Required | Authentication configuration (providers, theme, etc.) |
onSignInSuccess |
(AuthResult) -> Unit |
Required | Callback when sign-in succeeds |
onSignInFailure |
(AuthException) -> Unit |
Required | Callback when sign-in fails |
onSignInCancelled |
() -> Unit |
Required | Callback when user cancels authentication |
modifier |
Modifier |
Modifier |
Modifier for the composable |
authUI |
FirebaseAuthUI |
FirebaseAuthUI.getInstance() |
Custom FirebaseAuthUI instance (for multi-app support) |
emailLink |
String? |
null |
Email link for passwordless sign-in (see Email Link Sign-In) |
mfaConfiguration |
MfaConfiguration |
MfaConfiguration() |
MFA settings (see Multi-Factor Authentication) |
authenticatedContent |
@Composable ((AuthState, AuthSuccessUiContext) -> Unit)? |
null |
Optional content to show after successful authentication |
Using authenticatedContent:
Show custom UI after authentication completes, before navigating away:
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { result ->
// Called after authenticatedContent is dismissed
navigateToHome()
},
onSignInFailure = { exception ->
showError(exception)
},
onSignInCancelled = {
finish()
},
authenticatedContent = { state, uiContext ->
// Show a welcome screen or profile completion UI
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("Welcome, ${(state as? AuthState.Success)?.user?.displayName}!")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { uiContext.onContinue() }) {
Text("Continue to App")
}
}
}
)For maximum control, use the AuthFlowController:
class AuthActivity : ComponentActivity() {
private lateinit var controller: AuthFlowController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val authUI = FirebaseAuthUI.getInstance()
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
provider(AuthProvider.Google())
}
}
controller = authUI.createAuthFlow(configuration)
lifecycleScope.launch {
val state = controller.start()
handleAuthState(state)
}
}
private fun handleAuthState(state: AuthState) {
when (state) {
is AuthState.Success -> {
// Successfully signed in
val user = state.result.user
startActivity(Intent(this, MainActivity::class.java))
finish()
}
is AuthState.Error -> {
// Handle error
AlertDialog.Builder(this)
.setTitle("Authentication Failed")
.setMessage(state.exception.message)
.setPositiveButton("OK", null)
.show()
}
is AuthState.RequiresMfa -> {
// User needs to complete MFA challenge
showMfaChallengeDialog(state.resolver)
}
is AuthState.RequiresEmailVerification -> {
// Email verification needed
showEmailVerificationScreen(state.user)
}
is AuthState.Cancelled -> {
// User cancelled authentication
finish()
}
else -> {
// Handle other states
}
}
}
override fun onDestroy() {
super.onDestroy()
controller.dispose()
}
}For complete UI control while keeping authentication logic, use content slots:
@Composable
fun CustomEmailAuth() {
val emailConfig = AuthProvider.Email(
passwordValidationRules = listOf(
PasswordRule.MinimumLength(8),
PasswordRule.RequireDigit
)
)
EmailAuthScreen(
configuration = emailConfig,
onSuccess = { /* ... */ },
onError = { /* ... */ },
onCancel = { /* ... */ }
) { state ->
// Custom UI with full control
when (state.mode) {
EmailAuthMode.SignIn -> {
CustomSignInUI(state)
}
EmailAuthMode.SignUp -> {
CustomSignUpUI(state)
}
EmailAuthMode.ResetPassword -> {
CustomResetPasswordUI(state)
}
}
}
}
@Composable
fun CustomSignInUI(state: EmailAuthContentState) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Welcome Back!",
style = MaterialTheme.typography.headlineLarge
)
Spacer(modifier = Modifier.height(32.dp))
OutlinedTextField(
value = state.email,
onValueChange = state.onEmailChange,
label = { Text("Email") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = state.password,
onValueChange = state.onPasswordChange,
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
modifier = Modifier.fillMaxWidth()
)
if (state.error != null) {
Text(
text = state.error!!,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(top = 8.dp)
)
}
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = state.onSignInClick,
enabled = !state.isLoading,
modifier = Modifier.fillMaxWidth()
) {
if (state.isLoading) {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
} else {
Text("Sign In")
}
}
TextButton(onClick = state.onGoToResetPassword) {
Text("Forgot Password?")
}
TextButton(onClick = state.onGoToSignUp) {
Text("Create Account")
}
}
}Similarly, create custom phone authentication UI:
@Composable
fun CustomPhoneAuth() {
val phoneConfig = AuthProvider.Phone(defaultCountryCode = "US")
PhoneAuthScreen(
configuration = phoneConfig,
onSuccess = { /* ... */ },
onError = { /* ... */ },
onCancel = { /* ... */ }
) { state ->
when (state.step) {
PhoneAuthStep.EnterPhoneNumber -> {
CustomPhoneNumberInput(state)
}
PhoneAuthStep.EnterVerificationCode -> {
CustomVerificationCodeInput(state)
}
}
}
}Enable and configure Multi-Factor Authentication:
val mfaConfig = MfaConfiguration(
// Allowed MFA factors (default: [Sms, Totp])
allowedFactors = listOf(MfaFactor.Sms, MfaFactor.Totp),
// Optional: Require MFA enrollment (default: false)
requireEnrollment = false,
// Optional: Enable recovery codes (default: true)
enableRecoveryCodes = true
)
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
}
isMfaEnabled = true
}Prompt users to enroll in MFA after sign-in:
@Composable
fun MfaEnrollmentFlow() {
val currentUser = FirebaseAuth.getInstance().currentUser
if (currentUser != null) {
val mfaConfig = MfaConfiguration(
allowedFactors = listOf(MfaFactor.Sms, MfaFactor.Totp)
)
MfaEnrollmentScreen(
user = currentUser,
configuration = mfaConfig,
onEnrollmentComplete = {
Toast.makeText(context, "MFA enrolled successfully!", Toast.LENGTH_SHORT).show()
navigateToHome()
},
onSkip = {
navigateToHome()
}
)
}
}Or with custom UI:
MfaEnrollmentScreen(
user = currentUser,
configuration = mfaConfig,
onEnrollmentComplete = { /* ... */ },
onSkip = { /* ... */ }
) { state ->
when (state.step) {
MfaEnrollmentStep.SelectFactor -> {
CustomFactorSelectionUI(state)
}
MfaEnrollmentStep.ConfigureSms -> {
CustomSmsConfigurationUI(state)
}
MfaEnrollmentStep.ConfigureTotp -> {
CustomTotpConfigurationUI(state)
}
MfaEnrollmentStep.VerifyFactor -> {
CustomVerificationUI(state)
}
MfaEnrollmentStep.ShowRecoveryCodes -> {
CustomRecoveryCodesUI(state)
}
}
}Handle MFA challenges during sign-in. The challenge is automatically detected:
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { result ->
navigateToHome()
},
onSignInFailure = { exception ->
// MFA challenges are handled automatically by FirebaseAuthScreen
// But you can also handle them manually:
if (exception is AuthException.MfaRequiredException) {
showMfaChallengeScreen(exception.resolver)
}
}
)Or handle manually:
@Composable
fun ManualMfaChallenge(resolver: MultiFactorResolver) {
MfaChallengeScreen(
resolver = resolver,
onChallengeComplete = { assertion ->
// Complete sign-in with the assertion
lifecycleScope.launch {
try {
val result = resolver.resolveSignIn(assertion)
navigateToHome()
} catch (e: Exception) {
showError(e)
}
}
},
onCancel = {
navigateBack()
}
)
}FirebaseUI Auth provides flexible theming options to match your app's design:
AuthUITheme.Default/AuthUITheme.DefaultDark/AuthUITheme.Adaptive- Pre-configured Material Design 3 themes.copy()- Customize specific properties of the default themes (data class)fromMaterialTheme()- Inherit from your app's existing Material Theme- Custom theme - Full control over colors, typography, shapes, and provider button styles
FirebaseUI provides pre-configured themes for light and dark modes:
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
provider(AuthProvider.Google())
}
theme = AuthUITheme.Default // Light theme
// or
theme = AuthUITheme.DefaultDark // Dark theme
}AuthUITheme.Adaptive automatically switches between light and dark themes based on the system setting:
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
provider(AuthProvider.Google())
}
theme = AuthUITheme.Adaptive // Adapts to system dark mode
}This is the recommended approach for most apps as it provides a seamless experience that respects the user's system preferences.
Note: Adaptive is a @Composable property that evaluates to Default (light) or DefaultDark (dark) based on isSystemInDarkTheme() at composition time.
Use .copy() to customize specific properties of the default theme:
@Composable
fun AuthScreen() {
val customTheme = AuthUITheme.Adaptive.copy(
providerButtonShape = MaterialTheme.shapes.extraLarge // Pill-shaped buttons
)
val configuration = authUIConfiguration {
context = applicationContext
providers {
provider(AuthProvider.Google())
provider(AuthProvider.Email())
}
theme = customTheme
}
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { /* ... */ },
onSignInFailure = { /* ... */ },
onSignInCancelled = { /* ... */ }
)
}FirebaseUI Auth supports two theming patterns with clear precedence rules:
The simplest approach is to set the theme only in authUIConfiguration:
val configuration = authUIConfiguration {
context = applicationContext
providers {
provider(AuthProvider.Email())
}
theme = AuthUITheme.Adaptive // Set theme here
}
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { /* ... */ }
)When to use: This is the recommended pattern for most use cases. It's simple and explicit.
You can also wrap FirebaseAuthScreen with AuthUITheme:
val configuration = authUIConfiguration {
context = applicationContext
providers {
provider(AuthProvider.Email())
}
theme = AuthUITheme.Adaptive // Theme in configuration
}
AuthUITheme(theme = AuthUITheme.Adaptive) { // Optional wrapper
Surface(color = MaterialTheme.colorScheme.background) {
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { /* ... */ }
)
}
}When to use: Use this pattern when you have UI elements outside of FirebaseAuthScreen that need to share the same theme.
Understanding which theme applies is important:
-
Configuration theme takes precedence:
val configuration = authUIConfiguration { theme = AuthUITheme.Default // LIGHT theme } AuthUITheme(theme = AuthUITheme.DefaultDark) { // DARK wrapper FirebaseAuthScreen(configuration, ...) } // Result: FirebaseAuthScreen uses LIGHT theme (from configuration)
-
Wrapper as fallback:
val configuration = authUIConfiguration { // theme not specified (null) } AuthUITheme(theme = AuthUITheme.DefaultDark) { // DARK wrapper FirebaseAuthScreen(configuration, ...) } // Result: FirebaseAuthScreen inherits DARK theme from wrapper
-
Ultimate fallback:
val configuration = authUIConfiguration { // theme not specified (null) } FirebaseAuthScreen(configuration, ...) // No wrapper // Result: Uses AuthUITheme.Default (light theme)
Best Practice: For clarity and consistency, always set theme in authUIConfiguration. Use the wrapper only if you have additional UI outside FirebaseAuthScreen.
Use fromMaterialTheme() to automatically inherit your app's Material Design theme:
@Composable
fun App() {
MyAppTheme { // Your existing Material3 theme
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
}
theme = AuthUITheme.fromMaterialTheme() // Inherits colors, typography, shapes
}
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { /* ... */ }
)
}
}You can also customize while inheriting:
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Google())
provider(AuthProvider.Facebook())
}
theme = AuthUITheme.fromMaterialTheme(
providerButtonShape = RoundedCornerShape(16.dp) // Override button shape
)
}Build a theme from scratch with full control:
val customTheme = AuthUITheme(
colorScheme = darkColorScheme(
primary = Color(0xFF6200EE),
onPrimary = Color.White,
primaryContainer = Color(0xFF3700B3),
secondary = Color(0xFF03DAC6)
),
typography = Typography(
displayLarge = TextStyle(fontSize = 57.sp, fontWeight = FontWeight.Bold),
bodyLarge = TextStyle(fontSize = 16.sp)
),
shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(8.dp),
large = RoundedCornerShape(16.dp)
),
providerButtonShape = RoundedCornerShape(12.dp)
)
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
}
theme = customTheme
}Option 1: Using .copy() on default theme:
val customTheme = AuthUITheme.Default.copy(
providerButtonShape = RoundedCornerShape(12.dp) // Applies to all provider buttons
)
val configuration = authUIConfiguration {
providers = listOf(AuthProvider.Google(), AuthProvider.Facebook(), AuthProvider.Email())
theme = customTheme
}Option 2: Using fromMaterialTheme():
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Google())
provider(AuthProvider.Facebook())
}
theme = AuthUITheme.fromMaterialTheme(
providerButtonShape = RoundedCornerShape(16.dp)
)
}Option 3: Creating custom theme:
val customTheme = AuthUITheme(
colorScheme = MaterialTheme.colorScheme,
typography = MaterialTheme.typography,
shapes = MaterialTheme.shapes,
providerButtonShape = RoundedCornerShape(12.dp)
)
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Google())
provider(AuthProvider.Facebook())
provider(AuthProvider.Email())
}
theme = customTheme
}Customize specific provider buttons using the pre-defined ProviderStyleDefaults constants:
Using .copy() with default theme:
val customProviderStyles = mapOf(
"google.com" to ProviderStyleDefaults.Google.copy(
shape = RoundedCornerShape(8.dp),
elevation = 4.dp
),
"facebook.com" to ProviderStyleDefaults.Facebook.copy(
shape = RoundedCornerShape(24.dp),
elevation = 0.dp
)
)
val customTheme = AuthUITheme.Default.copy(
providerButtonShape = RoundedCornerShape(12.dp), // Default for all
providerStyles = customProviderStyles // Specific overrides
)
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Google())
provider(AuthProvider.Facebook())
}
theme = customTheme
}Using fromMaterialTheme():
val customProviderStyles = mapOf(
"google.com" to ProviderStyleDefaults.Google.copy(
shape = RoundedCornerShape(8.dp),
elevation = 4.dp
)
)
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Google())
provider(AuthProvider.Facebook())
}
theme = AuthUITheme.fromMaterialTheme(
providerButtonShape = RoundedCornerShape(12.dp),
providerStyles = customProviderStyles
)
}Real-world example combining global and per-provider customizations:
// Define custom styles for specific providers
val customProviderStyles = mapOf(
"google.com" to ProviderStyleDefaults.Google.copy(
shape = RoundedCornerShape(24.dp), // Pill-shaped Google button
elevation = 6.dp
),
"facebook.com" to ProviderStyleDefaults.Facebook.copy(
shape = RoundedCornerShape(8.dp), // Medium rounded Facebook button
elevation = 0.dp // Flat design
)
// Email provider will use the global providerButtonShape
)
// Customize default theme with global button shape and per-provider styles
val customTheme = AuthUITheme.Default.copy(
providerButtonShape = RoundedCornerShape(12.dp), // Global default for all buttons
providerStyles = customProviderStyles // Specific overrides
)
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Google()) // Uses custom shape (24.dp)
provider(AuthProvider.Facebook()) // Uses custom shape (8.dp)
provider(AuthProvider.Email()) // Uses global shape (12.dp)
}
theme = customTheme
}Customize the animations when navigating between screens using the AuthUITransitions object:
Slide animations:
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import com.firebase.ui.auth.configuration.AuthUITransitions
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
provider(AuthProvider.Google())
}
transitions = AuthUITransitions(
enterTransition = { slideInHorizontally { it } }, // Slide in from right
exitTransition = { slideOutHorizontally { -it } }, // Slide out to left
popEnterTransition = { slideInHorizontally { -it } }, // Slide in from left
popExitTransition = { slideOutHorizontally { it } } // Slide out to right
)
}Fade animations (default):
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import com.firebase.ui.auth.configuration.AuthUITransitions
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Phone())
}
transitions = AuthUITransitions(
enterTransition = { fadeIn() },
exitTransition = { fadeOut() },
popEnterTransition = { fadeIn() },
popExitTransition = { fadeOut() }
)
}Scale animations:
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import com.firebase.ui.auth.configuration.AuthUITransitions
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Facebook())
}
transitions = AuthUITransitions(
enterTransition = { fadeIn() + scaleIn(initialScale = 0.9f) },
exitTransition = { fadeOut() + scaleOut(targetScale = 0.9f) },
popEnterTransition = { fadeIn() + scaleIn(initialScale = 0.9f) },
popExitTransition = { fadeOut() + scaleOut(targetScale = 0.9f) }
)
}Vertical slide:
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import com.firebase.ui.auth.configuration.AuthUITransitions
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
}
transitions = AuthUITransitions(
enterTransition = { slideInVertically { it } }, // Slide up
exitTransition = { slideOutVertically { -it } } // Slide down
)
}Note: If not specified, default fade in/out transitions with 700ms duration are used.
Seamlessly upgrade anonymous users to permanent accounts:
// 1. Configure anonymous authentication with upgrade enabled
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Anonymous())
provider(AuthProvider.Email())
provider(AuthProvider.Google())
}
isAnonymousUpgradeEnabled = true
}
// 2. When user wants to create a permanent account, show auth UI
// The library automatically upgrades the anonymous account if one exists
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { result ->
// Anonymous account has been upgraded (if user was anonymous)!
Toast.makeText(this, "Account created!", Toast.LENGTH_SHORT).show()
}
)Enable passwordless email link authentication:
val emailProvider = AuthProvider.Email(
isEmailLinkSignInEnabled = true,
emailLinkActionCodeSettings = actionCodeSettings {
url = "https://example.com/auth"
handleCodeInApp = true
setAndroidPackageName(packageName, true, "12")
},
passwordValidationRules = emptyList()
)
val configuration = authUIConfiguration {
providers {
provider(emailProvider)
}
}High-Level API - Direct FirebaseAuthScreen usage:
// In your Activity that handles the deep link:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val authUI = FirebaseAuthUI.getInstance()
val emailLink = if (authUI.canHandleIntent(intent)) {
intent.data?.toString()
} else {
null
}
if (emailLink != null) {
setContent {
FirebaseAuthScreen(
configuration = configuration,
emailLink = emailLink,
onSignInSuccess = { result ->
// Email link sign-in successful
},
onSignInFailure = { exception ->
// Handle error
},
onSignInCancelled = {
finish()
}
)
}
}
}Low-Level API - Using AuthFlowController:
import com.firebase.ui.auth.util.EmailLinkConstants
// In your Activity that handles the deep link:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val authUI = FirebaseAuthUI.getInstance()
val emailLink = if (authUI.canHandleIntent(intent)) {
intent.data?.toString()
} else {
null
}
if (emailLink != null) {
val controller = authUI.createAuthFlow(configuration)
val intent = controller.createIntent(this).apply {
putExtra(EmailLinkConstants.EXTRA_EMAIL_LINK, emailLink)
}
authLauncher.launch(intent)
}
}
// Handle result
private val authLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
when (result.resultCode) {
Activity.RESULT_OK -> {
// Email link sign-in successful
}
Activity.RESULT_CANCELED -> {
// Handle error or cancellation
}
}
}Add the intent filter to your AndroidManifest.xml:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:pathPrefix="/auth" />
</intent-filter>Enforce custom password requirements:
val emailProvider = AuthProvider.Email(
emailLinkActionCodeSettings = null,
minimumPasswordLength = 10,
passwordValidationRules = listOf(
PasswordRule.MinimumLength(10),
PasswordRule.RequireUppercase,
PasswordRule.RequireLowercase,
PasswordRule.RequireDigit,
PasswordRule.RequireSpecialCharacter,
PasswordRule.Custom(
regex = Regex("^(?!.*password).*$"),
errorMessage = "Password cannot contain the word 'password'"
)
)
)FirebaseUI automatically integrates with Android's Credential Manager API to save and retrieve credentials. This enables:
- Automatic sign-in for returning users
- One-tap sign-in across apps
- Secure credential storage
Credential Manager is enabled by default. To disable:
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
}
isCredentialManagerEnabled = false
}Sign Out:
@Composable
fun SettingsScreen() {
val context = LocalContext.current
val authUI = remember { FirebaseAuthUI.getInstance() }
Button(
onClick = {
lifecycleScope.launch {
authUI.signOut(context)
// User is signed out, navigate to auth screen
navigateToAuth()
}
}
) {
Text("Sign Out")
}
}Delete Account:
Button(
onClick = {
lifecycleScope.launch {
try {
authUI.delete(context)
// Account deleted successfully
navigateToAuth()
} catch (e: Exception) {
when (e) {
is FirebaseAuthRecentLoginRequiredException -> {
// User needs to reauthenticate
showReauthenticationDialog()
}
else -> {
showError("Failed to delete account: ${e.message}")
}
}
}
}
}
) {
Text("Delete Account")
}FirebaseUI includes default English strings. To add custom localization:
class SpanishStringProvider(context: Context) : AuthUIStringProvider {
override fun signInWithEmail() = "Iniciar sesión con correo"
override fun signInWithGoogle() = "Iniciar sesión con Google"
override fun signInWithFacebook() = "Iniciar sesión con Facebook"
override fun invalidEmail() = "Correo inválido"
override fun weakPassword() = "Contraseña débil"
// ... implement all other required methods
}
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
}
stringProvider = SpanishStringProvider(context)
locale = Locale("es", "ES")
}Or override individual strings in your strings.xml:
<resources>
<!-- Override FirebaseUI strings -->
<string name="fui_sign_in_with_google">Sign in with Google</string>
<string name="fui_sign_in_with_email">Sign in with Email</string>
<string name="fui_invalid_email_address">Invalid email address</string>
<!-- See auth/src/main/res/values/strings.xml for all available strings -->
</resources>FirebaseUI provides a comprehensive exception hierarchy:
FirebaseAuthScreen(
configuration = configuration,
onSignInFailure = { exception ->
when (exception) {
is AuthException.NetworkException -> {
showSnackbar("No internet connection. Please check your network.")
}
is AuthException.InvalidCredentialsException -> {
showSnackbar("Invalid email or password.")
}
is AuthException.UserNotFoundException -> {
showSnackbar("No account found with this email.")
}
is AuthException.WeakPasswordException -> {
showSnackbar("Password is too weak. Please use a stronger password.")
}
is AuthException.EmailAlreadyInUseException -> {
showSnackbar("An account already exists with this email.")
}
is AuthException.TooManyRequestsException -> {
showSnackbar("Too many attempts. Please try again later.")
}
is AuthException.MfaRequiredException -> {
// Handled automatically by FirebaseAuthScreen
// or show custom MFA challenge
}
is AuthException.AccountLinkingRequiredException -> {
// Account needs to be linked
showAccountLinkingDialog(exception)
}
is AuthException.AuthCancelledException -> {
// User cancelled the flow
navigateBack()
}
is AuthException.UnknownException -> {
showSnackbar("An unexpected error occurred: ${exception.message}")
Log.e(TAG, "Auth error", exception)
}
}
}
)Use the ErrorRecoveryDialog for automatic error handling:
var errorState by remember { mutableStateOf<AuthException?>(null) }
errorState?.let { error ->
ErrorRecoveryDialog(
error = error,
onRetry = {
// Retry the authentication
errorState = null
retryAuthentication()
},
onDismiss = {
errorState = null
},
onRecover = { exception ->
// Custom recovery logic for specific errors
when (exception) {
is AuthException.AccountLinkingRequiredException -> {
linkAccounts(exception)
}
}
}
)
}The new Compose library has a completely different architecture. Here's how to migrate:
Old (9.x - View/Activity based):
// Old approach with startActivityForResult
Intent signInIntent = AuthUI.getInstance()
.createSignInIntentBuilder()
.setAvailableProviders(Arrays.asList(
new AuthUI.IdpConfig.EmailBuilder().build(),
new AuthUI.IdpConfig.GoogleBuilder().build()
))
.setTheme(R.style.AppTheme)
.build();
signInLauncher.launch(signInIntent);New (10.x - Compose based):
// New approach with Composable
val configuration = authUIConfiguration {
providers {
provider(AuthProvider.Email())
provider(AuthProvider.Google())
}
theme = AuthUITheme.fromMaterialTheme()
}
FirebaseAuthScreen(
configuration = configuration,
onSignInSuccess = { result -> /* ... */ },
onSignInFailure = { exception -> /* ... */ },
onSignInCancelled = { /* ... */ }
)Key Changes:
- Pure Compose - No more Activities or Intents, everything is Composable
- Configuration DSL - Use
authUIConfiguration {}instead ofcreateSignInIntentBuilder() - Provider Builders -
AuthProvider.Email()instead ofIdpConfig.EmailBuilder().build() - Callbacks - Direct callback parameters instead of
ActivityResultLauncher - Theming -
AuthUIThemeinstead ofR.styletheme resources - State Management - Reactive
Flow<AuthState>instead ofAuthStateListener
Migration Checklist:
- Update dependency to
firebase-ui-auth:10.0.0-beta02 - Convert Activities to Composables
- Replace Intent-based flow with
FirebaseAuthScreen - Update configuration from builder pattern to DSL
- Replace theme resources with
AuthUITheme - Update error handling from result codes to
AuthException - Remove
ActivityResultLauncherand use direct callbacks - Update sign-out/delete to use suspend functions
For a complete migration example, see the migration guide.
Contributions are welcome! Please read our contribution guidelines before submitting PRs.
FirebaseUI Auth is available under the Apache 2.0 license.
