Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,57 @@ const ui = initializeUI({
});
```

#### `legacyFetchSignInWithEmail`

The `legacyFetchSignInWithEmail` behavior augments OAuth `auth/account-exists-with-different-credential` flows by calling `fetchSignInMethodsForEmail(auth, email)` and storing the returned methods on the UI instance. In the packaged React and Angular screen components, this recovery state is rendered automatically as a modal on `SignInAuthScreen` and `OAuthScreen`.

The original pending credential is still preserved, so after the user signs in with the correct method, Firebase UI can continue the existing linking flow.

```ts
import { legacyFetchSignInWithEmail } from '@firebase-oss/ui-core';

const ui = initializeUI({
app,
behaviors: [legacyFetchSignInWithEmail()],
});
```

If you want full control over the UI, hide the built-in recovery component on the screen and read the recovery state directly with `useLegacySignInRecovery()`:

```tsx
import { GitHubSignInButton, GoogleSignInButton, SignInAuthScreen, useLegacySignInRecovery } from '@firebase-oss/ui-react';

function WrongProviderRecovery() {
const { recovery, clearRecovery } = useLegacySignInRecovery();

if (!recovery) {
return null;
}

return (
<div>
<p>You have previously signed in with a different method for {recovery.email}.</p>
{recovery.signInMethods.includes('google.com') && (
<GoogleSignInButton onSignIn={clearRecovery} />
)}
{recovery.signInMethods.includes('github.com') && (
<GitHubSignInButton onSignIn={clearRecovery} />
)}
</div>
);
}

export function CustomSignInScreen() {
return (
<SignInAuthScreen showLegacySignInRecovery={false}>
<WrongProviderRecovery />
</SignInAuthScreen>
);
}
```

Angular apps can hide the built-in recovery UI with `showLegacySignInRecovery="false"` and read the same state with `injectLegacySignInRecovery()` / `injectClearLegacySignInRecovery()`.

#### `oneTapSignIn`

The `oneTapSignIn` behavior triggers the [Google One Tap](https://developers.google.com/identity/gsi/web/guides/features) experience to render.
Expand Down Expand Up @@ -1061,6 +1112,7 @@ By default, any missing translations will fallback to English if not specified.
| onSignIn | `(user: User) => void?` | Callback when sign-in succeeds |
| onForgotPasswordClick | `() => void?` | Callback when forgot password link is clicked |
| onSignUpClick | `() => void?` | Callback when sign-up link is clicked |
| showLegacySignInRecovery | `boolean?` | Whether to show the built-in legacy sign-in recovery UI |

**`SignUpAuthScreen`**

Expand Down Expand Up @@ -1113,6 +1165,7 @@ By default, any missing translations will fallback to English if not specified.
|------|:----:|-------------|
| onSignIn | `(user: User) => void?` | Callback when sign-in succeeds |
| children | `React.ReactNode?` | Child components |
| showLegacySignInRecovery | `boolean?` | Whether to show the built-in legacy sign-in recovery UI |

**`OAuthButton`**

Expand Down Expand Up @@ -1189,6 +1242,10 @@ By default, any missing translations will fallback to English if not specified.
| asChild | `boolean?` | Render as child component using Slot |
| ...props | `ComponentProps<"button">` | Standard button HTML attributes |

**`LegacySignInRecovery`**

Default component for displaying suggested previous sign-in methods from `legacyFetchSignInWithEmail`.

**`Card`**

Card container component.
Expand Down Expand Up @@ -1251,6 +1308,12 @@ By default, any missing translations will fallback to English if not specified.

Returns `string | undefined`.

**`useLegacySignInRecovery`**

Gets the legacy sign-in recovery state populated by `legacyFetchSignInWithEmail`.

Returns `{ recovery: LegacySignInRecovery | undefined; clearRecovery: () => void }`.

**`useSignInAuthFormSchema`**

Creates a Zod schema for sign-in form validation.
Expand Down Expand Up @@ -1700,6 +1763,10 @@ By default, any missing translations will fallback to English if not specified.

Screen component for email/password sign-in.

| Input | Type | Description |
|-------|:----:|-------------|
| showLegacySignInRecovery | `boolean` | Whether to show the built-in legacy sign-in recovery UI |

| Output | Type | Description |
|--------|:----:|-------------|
| signIn | `EventEmitter<User>` | Emitted when sign-in succeeds |
Expand Down Expand Up @@ -1754,6 +1821,10 @@ By default, any missing translations will fallback to English if not specified.

Screen component for OAuth provider sign-in.

| Input | Type | Description |
|-------|:----:|-------------|
| showLegacySignInRecovery | `boolean` | Whether to show the built-in legacy sign-in recovery UI |

| Output | Type | Description |
|--------|:----:|-------------|
| onSignIn | `EventEmitter<User>` | Emitted when OAuth sign-in succeeds |
Expand Down Expand Up @@ -1904,6 +1975,12 @@ By default, any missing translations will fallback to English if not specified.

Component that displays redirect errors from Firebase UI authentication flow.

**`LegacySignInRecoveryComponent`**

Selector: `fui-legacy-sign-in-recovery`

Default component for displaying suggested previous sign-in methods from `legacyFetchSignInWithEmail`.

**`ContentComponent`**

Selector: `fui-content`
Expand All @@ -1922,6 +1999,18 @@ By default, any missing translations will fallback to English if not specified.

Returns `Signal<string \| undefined>`.

**`injectLegacySignInRecovery`**

Injects the legacy sign-in recovery state from the UI store as a signal.

Returns `Signal<LegacySignInRecovery \| undefined>`.

**`injectClearLegacySignInRecovery`**

Injects a callback that clears the current legacy sign-in recovery state.

Returns `() => void`.

**`injectTranslation`**

Injects a translated string for a given category and key.
Expand Down
3 changes: 2 additions & 1 deletion examples/react/src/firebase/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

"use client";

import { countryCodes, initializeUI, oneTapSignIn } from "@firebase-oss/ui-core";
import { countryCodes, initializeUI, legacyFetchSignInWithEmail, oneTapSignIn } from "@firebase-oss/ui-core";
import { getApps, initializeApp } from "firebase/app";
import { connectAuthEmulator, getAuth } from "firebase/auth";

Expand All @@ -30,6 +30,7 @@ export const ui = initializeUI({
app: firebaseApp,
behaviors: [
// autoAnonymousLogin(),
legacyFetchSignInWithEmail(),
oneTapSignIn({
clientId: "616577669988-led6l3rqek9ckn9t1unj4l8l67070fhp.apps.googleusercontent.com",
}),
Expand Down
7 changes: 7 additions & 0 deletions examples/react/src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import SignInAuthScreenPage from "./screens/sign-in-auth-screen";
import SignInAuthScreenWithHandlersPage from "./screens/sign-in-auth-screen-w-handlers";
import SignInAuthScreenWithOAuthPage from "./screens/sign-in-auth-screen-w-oauth";
import LegacyRecoveryDemoPage from "./screens/legacy-recovery-demo";
import SignUpAuthScreenPage from "./screens/sign-up-auth-screen";
import SignUpAuthScreenWithHandlersPage from "./screens/sign-up-auth-screen-w-handlers";
import SignUpAuthScreenWithOAuthPage from "./screens/sign-up-auth-screen-w-oauth";
Expand Down Expand Up @@ -32,6 +33,12 @@ export const routes = [
path: "/screens/sign-in-auth-screen-w-oauth",
component: SignInAuthScreenWithOAuthPage,
},
{
name: "Legacy Recovery Demo",
description: "Use this screen to test wrong-provider recovery for email/password and OAuth attempts.",
path: "/screens/legacy-recovery-demo",
component: LegacyRecoveryDemoPage,
},
{
name: "Sign Up Screen",
description: "A sign up screen with email and password.",
Expand Down
60 changes: 60 additions & 0 deletions examples/react/src/screens/legacy-recovery-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
AppleSignInButton,
FacebookSignInButton,
GitHubSignInButton,
GoogleSignInButton,
MicrosoftSignInButton,
SignInAuthScreen,
TwitterSignInButton,
YahooSignInButton,
} from "@firebase-oss/ui-react";
import { useNavigate } from "react-router";

export default function LegacyRecoveryDemoPage() {
const navigate = useNavigate();

return (
<div className="space-y-6">
<div className="max-w-sm mx-auto pt-10 text-sm text-gray-700 dark:text-gray-300 space-y-3">
<p className="font-medium text-base text-black dark:text-white">Legacy recovery demo</p>
<p>Use this screen to test wrong-provider recovery with both email/password and OAuth attempts.</p>
<p>
Suggested flow: create an account with Google first, sign out, then come back here and try the same email with
with email/password or another provider like GitHub.
</p>
</div>

<SignInAuthScreen
onSignIn={() => {
navigate("/");
}}
>
<div className="space-y-2">
<GoogleSignInButton />
<FacebookSignInButton />
<AppleSignInButton />
<GitHubSignInButton />
<MicrosoftSignInButton />
<TwitterSignInButton />
<YahooSignInButton />
</div>
</SignInAuthScreen>
</div>
);
}
Loading
Loading