Demo

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:
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:
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:
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 ThemeFirebaseUI 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:
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:
authUIConfiguration {} instead of createSignInIntentBuilder()AuthProvider.Email() instead of IdpConfig.EmailBuilder().build()ActivityResultLauncherAuthUITheme instead of R.style theme resourcesFlow<AuthState> instead of AuthStateListenerMigration Checklist:
firebase-ui-auth:10.0.0-beta02FirebaseAuthScreenAuthUIThemeAuthExceptionActivityResultLauncher and use direct callbacksFor 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.