Skip to content

feat(auth): reshape reauthContent into a ReauthContentState content slot - #2452

Open
demolaf wants to merge 1 commit into
version-10.0.0-beta04from
feat/reauth-content-state-slot
Open

feat(auth): reshape reauthContent into a ReauthContentState content slot#2452
demolaf wants to merge 1 commit into
version-10.0.0-beta04from
feat/reauth-content-state-slot

Conversation

@demolaf

@demolaf demolaf commented Aug 24, 2026

Copy link
Copy Markdown
Member

reauthContent was documented and shaped as a content slot alongside emailContent, phoneContent and the MFA slots, but it received only (AuthState.ReauthenticationRequired, onDismiss) and every API needed to build a reauthentication UI — filterToLinkedProviders, isReauthenticationMode, the federated provider driver — was internal or private. A custom slot could therefore only perform email/password reauthentication and had to dead-end for a Google- or OAuth-only account. The success handoff was also easy to get wrong: onDismiss reset auth state to Idle while retryOperation emitted AuthState.Success, so both orderings a caller would naturally reach either clobbered the success or cancelled the scope the retry ran in, silently dropping the sensitive operation.

reauthContent now receives a single ReauthContentState carrying the user, the reason, the providers already filtered to those linked to that user, and callbacks to select a provider or dismiss. The caller renders a provider chooser; the library owns credential exchange and dismiss/retry sequencing, so federated reauthentication is expressible from a custom slot and the ordering contract is no longer the caller's problem. Selecting AuthProvider.Email or AuthProvider.Phone hands off to the library's own sub-flow, honouring the caller's emailContent / phoneContent.

⚠️ Breaking changes

  • reauthContent takes a single ReauthContentState instead of (state, onDismiss).

  • AuthState.Success can no longer be constructed outside the library. It now records which uid a reauthentication re-proved, and that proof must not be forgeable by app code.

  • ReauthContentState.kt: new public state holder, following the MfaEnrollmentContentState conventions.

  • FirebaseAuthScreen.kt: the already-computed linked-provider list now reaches the slot instead of being discarded; the pending operation is consumed only for a library-published success on the same uid; provider selection and error-dialog recovery are inert while reauthentication is armed.

  • AuthState.kt: Success gains reauthenticatedUid and an internal constructor.

  • EmailAuthProvider+FirebaseAuthUI.kt, OAuthProvider+FirebaseAuthUI.kt: stamp reauthenticatedUid where the reauthenticated identity is known; account creation is rejected outright in reauthentication mode.

  • SignInUI.kt: the sign-up route is hidden in reauthentication mode, and Credential Manager autofill is skipped so a saved password for another account cannot be auto-submitted.

  • EmailAuthScreen.kt, ResetPasswordUI.kt, SignInEmailLinkUI.kt: the reauth email screen is prefilled with the signed-in address and renders it read-only in every mode that shows it.

Added FirebaseAuthScreenReauthContentStateTest and EmailAuthScreenReauthEmailLockTest, plus e2e coverage of reauthentication through the slot — each new test verified to be load-bearing by temporarily reverting the fix and confirming it fails.

Usage

FirebaseAuthScreen(
    configuration = configuration,
    onSignInSuccess = { },
    onSignInFailure = { },
    onSignInCancelled = { },
    reauthContent = { state ->
        AlertDialog(
            onDismissRequest = state.onDismiss,
            title = { Text(state.reason ?: "Verify your identity") },
            text = {
                Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
                    state.error?.let { Text(it, color = MaterialTheme.colorScheme.error) }
                    if (state.isLoading) CircularProgressIndicator()
                    state.providers.forEach { provider ->
                        Button(
                            onClick = { state.onProviderSelected(provider) },
                            enabled = !state.isLoading,
                        ) { Text("Continue with ${provider.providerName}") }
                    }
                }
            },
            confirmButton = {},
            dismissButton = { TextButton(onClick = state.onDismiss) { Text("Cancel") } },
        )
    },
)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust reauthentication flow in FirebaseUI Auth for Android, separating operation-level cancellations (AuthState.Cancelled) from flow-level aborts (AuthState.Aborted). It adds support for a custom, stateless reauthContent slot in FirebaseAuthScreen while keeping credential exchanges owned by the library, locks the email field to read-only during reauthentication, and resolves several state-resetting edge cases. The review feedback suggests making the OAuth reauthentication path more robust and fail-fast by explicitly throwing an exception if auth.currentUser is unexpectedly null, rather than silently failing with a safe call.

@demolaf
demolaf force-pushed the feat/reauth-content-state-slot branch 2 times, most recently from 5dfbe74 to 81b3b32 Compare August 25, 2026 00:38
@demolaf
demolaf changed the base branch from version-10.0.0-beta04-old to version-10.0.0-beta04 August 25, 2026 00:42
@demolaf
demolaf marked this pull request as ready for review August 25, 2026 09:22
@demolaf
demolaf force-pushed the feat/reauth-content-state-slot branch from 81b3b32 to 1e1858f Compare August 25, 2026 09:27
@demolaf
demolaf force-pushed the feat/reauth-content-state-slot branch from 1e1858f to 82c68c0 Compare August 25, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant