|
| 1 | +# Migration Guide |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +FirebaseUI for Web has been completely rewritten from the ground up. The previous version (v6) was a single JavaScript package that provided a monolithic authentication UI solution. The new version (v7) represents a modern, modular architecture that separates concerns and provides better flexibility for developers. |
| 6 | + |
| 7 | +### Architecture Changes |
| 8 | + |
| 9 | +**Previous Version (v6):** |
| 10 | +- Single JavaScript package (`firebaseui`) that handled both authentication logic and UI rendering |
| 11 | +- Tightly coupled to DOM manipulation and jQuery-like patterns |
| 12 | +- Limited customization options |
| 13 | +- Framework-agnostic but with a rigid structure |
| 14 | + |
| 15 | +**New Version (v7):** |
| 16 | +- **Framework-agnostic core package** (`@invertase/firebaseui-core`): Contains all authentication logic, state management, behaviors, and utilities without any UI dependencies |
| 17 | +- **Framework-specific packages**: Separate packages for React (`@invertase/firebaseui-react`), Angular (`@invertase/firebaseui-angular`), and Shadcn components |
| 18 | +- **Supporting packages**: Separate packages for styles (`@invertase/firebaseui-styles`) and translations (`@invertase/firebaseui-translations`) |
| 19 | +- **Composable architecture**: Components are designed to be composed together, allowing for greater flexibility |
| 20 | +- **Modern patterns**: Uses reactive stores (nanostores), TypeScript throughout, and modern framework patterns |
| 21 | + |
| 22 | +### Migration Path |
| 23 | + |
| 24 | +**Important:** There is no direct migration path from v6 to v7. This is a complete rewrite with a fundamentally different architecture and API. You cannot simply update the package version and expect your existing code to work. |
| 25 | + |
| 26 | +Instead, you will need to: |
| 27 | +1. Remove the old `firebaseui` package |
| 28 | +2. Install the appropriate new package(s) for your framework |
| 29 | +3. Rewrite your authentication UI components using the new API |
| 30 | +4. Update your configuration and styling approach |
| 31 | + |
| 32 | +### What This Guide Covers |
| 33 | + |
| 34 | +This migration guide maps features and concepts from the old [v6 version](https://github.com/firebase/firebaseui-web/tree/v6) to the new v7 rewrite, helping you understand: |
| 35 | +- How authentication methods translate between versions |
| 36 | +- How configuration options map to the new behaviors system |
| 37 | +- How UI customization works in the new architecture |
| 38 | +- How to achieve similar functionality with the new component-based approach |
| 39 | + |
| 40 | +## Migrating |
| 41 | + |
| 42 | +### 1. Installing Packages |
| 43 | + |
| 44 | +First, remove the old `firebaseui` package and install the appropriate new package(s) for your framework: |
| 45 | + |
| 46 | +<details> |
| 47 | + <summary>React</summary> |
| 48 | + |
| 49 | + Remove the old package: |
| 50 | + ```bash |
| 51 | + npm uninstall firebaseui |
| 52 | + ``` |
| 53 | + |
| 54 | + Install the new React package: |
| 55 | + ```bash |
| 56 | + npm install @invertase/firebaseui-react |
| 57 | + ``` |
| 58 | + |
| 59 | + The package automatically includes the core package as a dependency, so you don't need to install `@invertase/firebaseui-core` separately. |
| 60 | +</details> |
| 61 | + |
| 62 | +<details> |
| 63 | + <summary>Angular</summary> |
| 64 | + |
| 65 | + Remove the old package: |
| 66 | + ```bash |
| 67 | + npm uninstall firebaseui |
| 68 | + ``` |
| 69 | + |
| 70 | + Install the new Angular package: |
| 71 | + ```bash |
| 72 | + npm install @invertase/firebaseui-angular |
| 73 | + ``` |
| 74 | + |
| 75 | + **Note:** The Angular package requires [AngularFire](https://github.com/angular/angularfire) to be installed and configured first. |
| 76 | +</details> |
| 77 | + |
| 78 | +<details> |
| 79 | + <summary>Shadcn</summary> |
| 80 | + |
| 81 | + Remove the old package: |
| 82 | + ```bash |
| 83 | + npm uninstall firebaseui |
| 84 | + ``` |
| 85 | + |
| 86 | + Ensure you have [installed and setup](https://ui.shadcn.com/docs/installation) Shadcn in your project first. |
| 87 | + |
| 88 | + Add the Firebase UI registry to your `components.json`: |
| 89 | + ```json |
| 90 | + { |
| 91 | + ... |
| 92 | + "registries": { |
| 93 | + "@firebase": "https://fir-ui-shadcn-registry.web.app/r/{name}.json" |
| 94 | + } |
| 95 | + } |
| 96 | + ``` |
| 97 | + |
| 98 | + Then add components as needed: |
| 99 | + ```bash |
| 100 | + npx shadcn@latest add @firebase/sign-in-auth-screen |
| 101 | + ``` |
| 102 | + |
| 103 | + This will automatically install all required dependencies. |
| 104 | +</details> |
| 105 | + |
| 106 | +### 2. Initialization |
| 107 | + |
| 108 | +The initialization process is fundamentally different between v6 and v7. |
| 109 | + |
| 110 | +**Old Way (v6):** |
| 111 | +```javascript |
| 112 | +// Initialize the FirebaseUI Widget using Firebase. |
| 113 | +var ui = new firebaseui.auth.AuthUI(firebase.auth()); |
| 114 | + |
| 115 | +// The start method will wait until the DOM is loaded. |
| 116 | +ui.start('#firebaseui-auth-container', uiConfig); |
| 117 | +``` |
| 118 | + |
| 119 | +**New Way (v7):** |
| 120 | + |
| 121 | +<details> |
| 122 | + <summary>React (+Shadcn)</summary> |
| 123 | + |
| 124 | + ```tsx |
| 125 | + import { initializeApp } from 'firebase/app'; |
| 126 | + import { initializeUI } from '@invertase/firebaseui-core'; |
| 127 | + import { FirebaseUIProvider } from '@invertase/firebaseui-react'; |
| 128 | + |
| 129 | + const app = initializeApp({ ... }); |
| 130 | + |
| 131 | + const ui = initializeUI({ |
| 132 | + app, |
| 133 | + // behaviors and other configuration go here |
| 134 | + }); |
| 135 | + |
| 136 | + function App() { |
| 137 | + return ( |
| 138 | + <FirebaseUIProvider ui={ui}> |
| 139 | + {/* Your app components */} |
| 140 | + </FirebaseUIProvider> |
| 141 | + ); |
| 142 | + } |
| 143 | + ``` |
| 144 | +</details> |
| 145 | + |
| 146 | +<details> |
| 147 | + <summary>Angular</summary> |
| 148 | + |
| 149 | + ```ts |
| 150 | + import { provideFirebaseApp, initializeApp } from '@angular/fire/app'; |
| 151 | + import { initializeUI } from '@invertase/firebaseui-core'; |
| 152 | + |
| 153 | + export const appConfig: ApplicationConfig = { |
| 154 | + providers: [ |
| 155 | + provideFirebaseApp(() => initializeApp({ ... })), |
| 156 | + provideFirebaseUI((apps) => initializeUI({ |
| 157 | + app: apps[0], |
| 158 | + // behaviors and other configuration go here |
| 159 | + })), |
| 160 | + ] |
| 161 | + }; |
| 162 | + ``` |
| 163 | +</details> |
| 164 | + |
| 165 | +### 3. Configuration Options Migration |
| 166 | + |
| 167 | +The following table maps v6 configuration options to their v7 equivalents: |
| 168 | + |
| 169 | +| v6 Option | Migration Guide | |
| 170 | +|----------|------------------| |
| 171 | +| `autoUpgradeAnonymousUsers` | **Use the `autoUpgradeAnonymousUsers` behavior.**<br/><br/>Import `autoUpgradeAnonymousUsers` from `@invertase/firebaseui-core` and add it to your behaviors array:<br/>`behaviors: [autoUpgradeAnonymousUsers({ async onUpgrade(ui, oldUserId, credential) { /* handle merge */ } })]`<br/><br/>The `onUpgrade` callback replaces the `signInFailure` callback for handling merge conflicts. | |
| 172 | +| `callbacks` | **Use component props/events instead.**<br/><br/>v6 callbacks like `signInSuccessWithAuthResult`, `signInFailure`, etc. are replaced by component event handlers:<br/><br/>**React:** `onSignIn={(user) => { ... }}`, `onSignUp={(user) => { ... }}`, `onForgotPasswordClick={() => { ... }}`<br/><br/>**Angular:** `(signIn)="onSignIn($event)"`, `(signUp)="onSignUp($event)"`, `(forgotPassword)="onForgotPassword()"`<br/><br/>These are passed directly to the components you use, giving you more control over the flow. | |
| 173 | +| `credentialHelper` | **Use the `oneTapSignIn` behavior.**<br/><br/>The credential helper (Account Chooser) from v6 is replaced by Google One Tap in v7. Import `oneTapSignIn` from `@invertase/firebaseui-core` and add it to your behaviors array:<br/>`behaviors: [oneTapSignIn({ clientId: '...', autoSelect: false, cancelOnTapOutside: false })]`<br/><br/>**Note:** This requires Google Sign In to be enabled in Firebase Console. Get the `clientId` from "Web SDK configuration" settings. See [Google One Tap documentation](https://developers.google.com/identity/gsi/web/reference/js-reference) for all configuration options. | |
| 174 | +| `queryParameterForSignInSuccessUrl` | **Handle in your routing logic.**<br/><br/>v7 doesn't have built-in URL parameter handling. Instead, handle redirects in your `onSignIn` callback by reading URL params:<br/><br/>**React/Angular:** `const urlParams = new URLSearchParams(window.location.search);`<br/>`const redirectUrl = urlParams.get('signInSuccessUrl') || '/dashboard';`<br/>`window.location.href = redirectUrl;`<br/><br/>**Angular (with Router):** Use `ActivatedRoute` to read query params and `Router` to navigate. | |
| 175 | +| `queryParameterForWidgetMode` | **Not applicable.**<br/><br/>v7 doesn't use widget modes. Instead, you explicitly render the components you need:<br/><br/>**React:** `<SignInAuthScreen />`, `<SignUpAuthScreen />`<br/><br/>**Angular:** `<fui-sign-in-auth-screen>`, `<fui-sign-up-auth-screen>` | |
| 176 | +| `signInFlow` | **Use provider strategy behaviors.**<br/><br/>Replace `signInFlow: 'redirect'` with:<br/>`import { providerRedirectStrategy } from '@invertase/firebaseui-core'`<br/>`behaviors: [providerRedirectStrategy()]`<br/><br/>Replace `signInFlow: 'popup'` with:<br/>`import { providerPopupStrategy } from '@invertase/firebaseui-core'`<br/>`behaviors: [providerPopupStrategy()]`<br/><br/>**Note:** `popup` is the default strategy in v7. | |
| 177 | +| `immediateFederatedRedirect` | **Control via component rendering.**<br/><br/>v7 doesn't have this option. Instead, you control whether to show OAuth buttons or redirect immediately by conditionally rendering components:<br/><br/>**React:** `{singleProvider ? <Navigate to="/oauth-redirect" /> : <OAuthScreen onSignIn={handleSignIn} />}`<br/><br/>**Angular:** Use `*ngIf` or `@if` to conditionally render `<fui-oauth-screen>` or use `Router` to navigate directly. | |
| 178 | +| `signInOptions` | **Use OAuth button components directly.**<br/><br/>v6's `signInOptions` array is replaced by explicitly rendering the OAuth provider buttons you want:<br/><br/>**React:** Import `GoogleSignInButton`, `FacebookSignInButton`, `AppleSignInButton` from `@invertase/firebaseui-react` and render them inside `<OAuthScreen>`.<br/><br/>**Angular:** Import `GoogleSignInButtonComponent`, `FacebookSignInButtonComponent`, `AppleSignInButtonComponent` from `@invertase/firebaseui-angular` and use selectors `<fui-google-sign-in-button>`, `<fui-facebook-sign-in-button>`, `<fui-apple-sign-in-button>` inside `<fui-oauth-screen>`.<br/><br/>The order you place the buttons determines their display order. | |
| 179 | +| `signInSuccessUrl` | **Handle in `onSignIn` callback.**<br/><br/>Instead of a configuration option, handle redirects in your component's `onSignIn` callback:<br/><br/>**React:** `<SignInAuthScreen onSignIn={(user) => { window.location.href = '/dashboard'; }} />`<br/><br/>**Angular:** `<fui-sign-in-auth-screen (signIn)="onSignIn($event)" />` with `onSignIn(user: User) { this.router.navigate(['/dashboard']); }`<br/><br/>*Required in v6 when `signInSuccessWithAuthResult` callback is not used or returns `true`. | |
| 180 | +| `tosUrl` | **Pass via `policies` prop.**<br/><br/>**React:** Pass `policies={{ termsOfServiceUrl: 'https://example.com/tos', privacyPolicyUrl: 'https://example.com/privacy' }}` to `<FirebaseUIProvider>`.<br/><br/>**Angular:** Use `provideFirebaseUIPolicies(() => ({ termsOfServiceUrl: '...', privacyPolicyUrl: '...' }))`.<br/><br/>The policies are automatically rendered in auth forms and screens. | |
| 181 | +| `privacyPolicyUrl` | **Pass via `policies` prop.**<br/><br/>See `tosUrl` above - both URLs are passed together in the `policies` object. | |
| 182 | +| `adminRestrictedOperation` | **Handle in your UI logic.**<br/><br/>v7 doesn't have built-in support for this GCIP-specific feature. You'll need to:<br/>(1) Check if sign-up is disabled in your Firebase project settings<br/>(2) Handle the `auth/admin-restricted-operation` error in your error handling<br/>(3) Display appropriate messaging to users when sign-up attempts are blocked<br/><br/>You can check for this error in your `onSignUp` or form error handlers and display custom UI accordingly. | |
| 183 | + |
| 184 | +### Additional Configuration |
| 185 | + |
| 186 | +#### Configure Phone Provider |
| 187 | + |
| 188 | +In v6, phone authentication country code configuration was handled via the `signInOptions` configuration. In v7, this is controlled by the `countryCodes` behavior. |
| 189 | + |
| 190 | +**v6:** |
| 191 | +```javascript |
| 192 | +signInOptions: [ |
| 193 | + { |
| 194 | + provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID, |
| 195 | + defaultCountry: 'GB', |
| 196 | + whitelistedCountries: ['GB', 'US', 'FR'] |
| 197 | + } |
| 198 | +] |
| 199 | +``` |
| 200 | + |
| 201 | +**v7:** |
| 202 | +Use the `countryCodes` behavior to configure allowed countries and default country: |
| 203 | + |
| 204 | +```ts |
| 205 | +import { countryCodes } from '@invertase/firebaseui-core'; |
| 206 | + |
| 207 | +const ui = initializeUI({ |
| 208 | + app, |
| 209 | + behaviors: [ |
| 210 | + countryCodes({ |
| 211 | + allowedCountries: ['GB', 'US', 'FR'], // only allow Great Britain, USA and France |
| 212 | + defaultCountry: 'GB', // GB is default |
| 213 | + }), |
| 214 | + ], |
| 215 | +}); |
| 216 | +``` |
| 217 | + |
| 218 | +The `countryCodes` behavior affects all phone authentication flows, including regular phone sign-in and multi-factor authentication (MFA) enrollment. The `CountrySelector` component automatically uses these settings to filter and display available countries. |
| 219 | + |
| 220 | +#### Sign In Flows |
| 221 | + |
| 222 | +In v6, you configured the sign-in flow (popup vs redirect) via the `signInFlow` configuration option. In v7, this is controlled by provider strategy behaviors. |
| 223 | + |
| 224 | +**v6:** |
| 225 | +```javascript |
| 226 | +var uiConfig = { |
| 227 | + signInFlow: 'popup', // or 'redirect' |
| 228 | + // ... |
| 229 | +}; |
| 230 | +``` |
| 231 | + |
| 232 | +**v7:** |
| 233 | +Use the `providerPopupStrategy` (default) or `providerRedirectStrategy` behaviors: |
| 234 | + |
| 235 | +```ts |
| 236 | +import { providerPopupStrategy, providerRedirectStrategy } from '@invertase/firebaseui-core'; |
| 237 | + |
| 238 | +// For popup flow (default) |
| 239 | +const ui = initializeUI({ |
| 240 | + app, |
| 241 | + behaviors: [providerPopupStrategy()], |
| 242 | +}); |
| 243 | + |
| 244 | +// For redirect flow |
| 245 | +const ui = initializeUI({ |
| 246 | + app, |
| 247 | + behaviors: [providerRedirectStrategy()], |
| 248 | +}); |
| 249 | +``` |
| 250 | + |
| 251 | +**Note:** The popup strategy is the default in v7. If you don't specify a strategy, popup will be used. The strategy applies to all OAuth providers (Google, Facebook, Apple, etc.). |
| 252 | + |
| 253 | +#### Multi-tenancy Support |
| 254 | + |
| 255 | +v7 supports multi-tenancy by allowing you to pass a custom `Auth` instance with a `tenantId` configured to `initializeUI`. |
| 256 | + |
| 257 | +**v6:** |
| 258 | +```javascript |
| 259 | +var tenantAuth = firebase.auth(app).tenantId = 'tenant-id'; |
| 260 | +var ui = new firebaseui.auth.AuthUI(tenantAuth); |
| 261 | +``` |
| 262 | + |
| 263 | +**v7:** |
| 264 | + |
| 265 | +**React:** |
| 266 | +```tsx |
| 267 | +import { getAuth } from 'firebase/auth'; |
| 268 | +import { initializeUI } from '@invertase/firebaseui-core'; |
| 269 | + |
| 270 | +const auth = getAuth(app); |
| 271 | +auth.tenantId = 'tenant-id'; |
| 272 | + |
| 273 | +const ui = initializeUI({ |
| 274 | + app, |
| 275 | + auth, // Pass the auth instance with tenantId |
| 276 | +}); |
| 277 | +``` |
| 278 | + |
| 279 | +**Angular:** |
| 280 | +```ts |
| 281 | +import { getAuth } from 'firebase/auth'; |
| 282 | +import { initializeUI } from '@invertase/firebaseui-core'; |
| 283 | + |
| 284 | +export const appConfig: ApplicationConfig = { |
| 285 | + providers: [ |
| 286 | + provideFirebaseApp(() => initializeApp({ ... })), |
| 287 | + provideAuth(() => { |
| 288 | + const auth = getAuth(); |
| 289 | + auth.tenantId = 'tenant-id'; |
| 290 | + return auth; |
| 291 | + }), |
| 292 | + provideFirebaseUI((apps) => { |
| 293 | + const auth = getAuth(apps[0]); |
| 294 | + auth.tenantId = 'tenant-id'; |
| 295 | + return initializeUI({ |
| 296 | + app: apps[0], |
| 297 | + auth, |
| 298 | + }); |
| 299 | + }), |
| 300 | + ], |
| 301 | +}; |
| 302 | +``` |
| 303 | + |
| 304 | +#### Enabling Anonymous User Upgrade |
| 305 | + |
| 306 | +In v6, anonymous user upgrade was configured via the `autoUpgradeAnonymousUsers` option. In v7, this is handled by the `autoUpgradeAnonymousUsers` behavior. |
| 307 | + |
| 308 | +**v6:** |
| 309 | +```javascript |
| 310 | +var uiConfig = { |
| 311 | + autoUpgradeAnonymousUsers: true, |
| 312 | + callbacks: { |
| 313 | + signInFailure: function(error) { |
| 314 | + // Handle merge conflicts |
| 315 | + } |
| 316 | + } |
| 317 | +}; |
| 318 | +``` |
| 319 | + |
| 320 | +**v7:** |
| 321 | +Use the `autoUpgradeAnonymousUsers` behavior: |
| 322 | + |
| 323 | +```ts |
| 324 | +import { autoUpgradeAnonymousUsers } from '@invertase/firebaseui-core'; |
| 325 | + |
| 326 | +const ui = initializeUI({ |
| 327 | + app, |
| 328 | + behaviors: [ |
| 329 | + autoUpgradeAnonymousUsers({ |
| 330 | + async onUpgrade(ui, oldUserId, credential) { |
| 331 | + // Handle account merge logic |
| 332 | + // e.g., migrate data from oldUserId to new user |
| 333 | + console.log(`Upgrading anonymous user ${oldUserId} to ${credential.user.uid}`); |
| 334 | + }, |
| 335 | + }), |
| 336 | + ], |
| 337 | +}); |
| 338 | +``` |
| 339 | + |
| 340 | +The behavior automatically upgrades anonymous users when they sign in with any credential (email/password, OAuth, phone, etc.). The `onUpgrade` callback is optional and allows you to perform custom logic during the upgrade, such as migrating user data. |
| 341 | + |
| 342 | +#### Handling Anonymous User Upgrade Merge Conflicts |
| 343 | + |
| 344 | +In v6, merge conflicts (when an account already exists with the same credential) were handled via the `signInFailure` callback. In v7, the upgrade process handles this differently. |
| 345 | + |
| 346 | +**How it works in v7:** |
| 347 | + |
| 348 | +When an anonymous user attempts to sign in with a credential that's already associated with an existing account, Firebase Auth will automatically link the anonymous account to the existing account. The upgrade process: |
| 349 | + |
| 350 | +1. **Stores the anonymous user ID** in `localStorage` before redirect flows (for OAuth redirects) |
| 351 | +2. **Automatically links** the anonymous account to the existing account |
| 352 | +3. **Calls the `onUpgrade` callback** (if provided) with both the old anonymous user ID and the new credential |
| 353 | +4. **Cleans up** the stored anonymous user ID from `localStorage` |
| 354 | + |
| 355 | +**Example:** |
| 356 | +```ts |
| 357 | +const ui = initializeUI({ |
| 358 | + app, |
| 359 | + behaviors: [ |
| 360 | + autoUpgradeAnonymousUsers({ |
| 361 | + async onUpgrade(ui, oldUserId, credential) { |
| 362 | + // oldUserId is the anonymous user's ID |
| 363 | + // credential.user.uid is the existing account's ID after linking |
| 364 | + |
| 365 | + // Migrate any data from the anonymous account |
| 366 | + await migrateUserData(oldUserId, credential.user.uid); |
| 367 | + |
| 368 | + // The anonymous account is now linked to the existing account |
| 369 | + // The user is signed in with their existing account |
| 370 | + }, |
| 371 | + }), |
| 372 | + ], |
| 373 | +}); |
| 374 | +``` |
| 375 | + |
| 376 | +**Note:** If a merge conflict occurs and the linking fails (e.g., due to account linking restrictions), Firebase Auth will throw an error that you can handle in your error handling logic. The `onUpgrade` callback will only be called if the upgrade is successful. |
| 377 | + |
0 commit comments