From 946429661361e904a6f4574569ce2167910304c5 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Mon, 6 Mar 2023 10:25:19 +0100 Subject: [PATCH 1/3] fix(auth, web): fix currentUser being null when using emulator or named instance --- .../firebase_auth/example/lib/auth.dart | 46 +++++++++---------- .../firebase_auth/example/lib/main.dart | 11 +++-- .../firebase_auth/example/lib/profile.dart | 8 ++-- .../lib/firebase_auth_web.dart | 36 +++++++++++++-- .../lib/src/firebase_core_web.dart | 8 ++-- 5 files changed, 72 insertions(+), 37 deletions(-) diff --git a/packages/firebase_auth/firebase_auth/example/lib/auth.dart b/packages/firebase_auth/firebase_auth/example/lib/auth.dart index 3288d0f25ebe..2110b0c8c79e 100644 --- a/packages/firebase_auth/firebase_auth/example/lib/auth.dart +++ b/packages/firebase_auth/firebase_auth/example/lib/auth.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_auth_example/main.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -14,8 +15,6 @@ import 'package:google_sign_in/google_sign_in.dart'; typedef OAuthSignIn = void Function(); -final FirebaseAuth _auth = FirebaseAuth.instance; - // If set to true, the app will request notification permissions to use // silent verification for SMS MFA instead of Recaptcha. const withSilentVerificationSMSMFA = true; @@ -63,10 +62,10 @@ class AuthGate extends StatefulWidget { const AuthGate({Key? key}) : super(key: key); @override - State createState() => _AuthGateState(); + State createState() => authGateState(); } -class _AuthGateState extends State { +class authGateState extends State { TextEditingController emailController = TextEditingController(); TextEditingController passwordController = TextEditingController(); TextEditingController phoneController = TextEditingController(); @@ -91,7 +90,7 @@ class _AuthGateState extends State { void initState() { super.initState(); - if (withSilentVerificationSMSMFA) { + if (withSilentVerificationSMSMFA && !kIsWeb) { FirebaseMessaging messaging = FirebaseMessaging.instance; messaging.requestPermission(); } @@ -371,7 +370,7 @@ class _AuthGateState extends State { if (email != null) { try { - await _auth.sendPasswordResetEmail(email: email!); + await auth.sendPasswordResetEmail(email: email!); ScaffoldSnackbar.of(context).show('Password reset email is sent'); } catch (e) { ScaffoldSnackbar.of(context).show('Error resetting'); @@ -383,7 +382,7 @@ class _AuthGateState extends State { setIsLoading(); try { - await _auth.signInAnonymously(); + await auth.signInAnonymously(); } on FirebaseAuthException catch (e) { setState(() { error = '${e.message}'; @@ -411,7 +410,6 @@ class _AuthGateState extends State { if (firstHint is! PhoneMultiFactorInfo) { return; } - final auth = FirebaseAuth.instance; await auth.verifyPhoneNumber( multiFactorSession: e.resolver.session, multiFactorInfo: firstHint, @@ -457,12 +455,12 @@ class _AuthGateState extends State { if (formKey.currentState?.validate() ?? false) { setIsLoading(); if (mode == AuthMode.login) { - await _auth.signInWithEmailAndPassword( + await auth.signInWithEmailAndPassword( email: emailController.text, password: passwordController.text, ); } else if (mode == AuthMode.register) { - await _auth.createUserWithEmailAndPassword( + await auth.createUserWithEmailAndPassword( email: emailController.text, password: passwordController.text, ); @@ -480,14 +478,14 @@ class _AuthGateState extends State { } else { if (kIsWeb) { final confirmationResult = - await _auth.signInWithPhoneNumber(phoneController.text); + await auth.signInWithPhoneNumber(phoneController.text); final smsCode = await getSmsCodeFromUser(context); if (smsCode != null) { await confirmationResult.confirm(smsCode); } } else { - await _auth.verifyPhoneNumber( + await auth.verifyPhoneNumber( phoneNumber: phoneController.text, verificationCompleted: (_) {}, verificationFailed: (e) { @@ -507,7 +505,7 @@ class _AuthGateState extends State { try { // Sign the user in (or link) with the credential - await _auth.signInWithCredential(credential); + await auth.signInWithCredential(credential); } on FirebaseAuthException catch (e) { setState(() { error = e.message ?? ''; @@ -540,7 +538,7 @@ class _AuthGateState extends State { ); // Once signed in, return the UserCredential - await _auth.signInWithCredential(credential); + await auth.signInWithCredential(credential); } } @@ -548,9 +546,9 @@ class _AuthGateState extends State { TwitterAuthProvider twitterProvider = TwitterAuthProvider(); if (kIsWeb) { - await _auth.signInWithPopup(twitterProvider); + await auth.signInWithPopup(twitterProvider); } else { - await _auth.signInWithProvider(twitterProvider); + await auth.signInWithProvider(twitterProvider); } } @@ -560,9 +558,9 @@ class _AuthGateState extends State { if (kIsWeb) { // Once signed in, return the UserCredential - await _auth.signInWithPopup(appleProvider); + await auth.signInWithPopup(appleProvider); } else { - await _auth.signInWithProvider(appleProvider); + await auth.signInWithProvider(appleProvider); } } @@ -571,9 +569,9 @@ class _AuthGateState extends State { if (kIsWeb) { // Once signed in, return the UserCredential - await _auth.signInWithPopup(yahooProvider); + await auth.signInWithPopup(yahooProvider); } else { - await _auth.signInWithProvider(yahooProvider); + await auth.signInWithProvider(yahooProvider); } } @@ -581,9 +579,9 @@ class _AuthGateState extends State { final githubProvider = GithubAuthProvider(); if (kIsWeb) { - await _auth.signInWithPopup(githubProvider); + await auth.signInWithPopup(githubProvider); } else { - await _auth.signInWithProvider(githubProvider); + await auth.signInWithProvider(githubProvider); } } @@ -591,9 +589,9 @@ class _AuthGateState extends State { final microsoftProvider = MicrosoftAuthProvider(); if (kIsWeb) { - await _auth.signInWithPopup(microsoftProvider); + await auth.signInWithPopup(microsoftProvider); } else { - await _auth.signInWithProvider(microsoftProvider); + await auth.signInWithProvider(microsoftProvider); } } } diff --git a/packages/firebase_auth/firebase_auth/example/lib/main.dart b/packages/firebase_auth/firebase_auth/example/lib/main.dart index 654e445d0fc5..2f98f7130729 100755 --- a/packages/firebase_auth/firebase_auth/example/lib/main.dart +++ b/packages/firebase_auth/firebase_auth/example/lib/main.dart @@ -14,6 +14,9 @@ import 'profile.dart'; /// See https://firebase.flutter.dev/docs/auth/start/#optional-prototype-and-test-with-firebase-local-emulator-suite bool shouldUseFirebaseEmulator = false; +late final FirebaseApp app; +late final FirebaseAuth auth; + // Requires that the Firebase Auth emulator is running locally // e.g via `melos run firebase:emulator`. Future main() async { @@ -21,12 +24,14 @@ Future main() async { // We're using the manual installation on non-web platforms since Google sign in plugin doesn't yet support Dart initialization. // See related issue: https://github.com/flutter/flutter/issues/96391 - await Firebase.initializeApp( + // We store the app and auth to make testing with a named instance easier. + app = await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + auth = FirebaseAuth.instanceFor(app: app); if (shouldUseFirebaseEmulator) { - await FirebaseAuth.instance.useAuthEmulator('localhost', 9099); + await auth.useAuthEmulator('localhost', 9099); } runApp(const AuthExampleApp()); @@ -73,7 +78,7 @@ class AuthExampleApp extends StatelessWidget { ? constraints.maxWidth / 2 : constraints.maxWidth, child: StreamBuilder( - stream: FirebaseAuth.instance.authStateChanges(), + stream: auth.authStateChanges(), builder: (context, snapshot) { if (snapshot.hasData) { return const ProfilePage(); diff --git a/packages/firebase_auth/firebase_auth/example/lib/profile.dart b/packages/firebase_auth/firebase_auth/example/lib/profile.dart index 856c4f3bbcbf..f9ada27592a3 100644 --- a/packages/firebase_auth/firebase_auth/example/lib/profile.dart +++ b/packages/firebase_auth/firebase_auth/example/lib/profile.dart @@ -5,6 +5,7 @@ import 'dart:developer'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_auth_example/main.dart'; import 'package:flutter/material.dart'; import 'package:google_sign_in/google_sign_in.dart'; @@ -36,12 +37,12 @@ class _ProfilePageState extends State { @override void initState() { - user = FirebaseAuth.instance.currentUser!; + user = auth.currentUser!; controller = TextEditingController(text: user.displayName); controller.addListener(_onNameChanged); - FirebaseAuth.instance.userChanges().listen((event) { + auth.userChanges().listen((event) { if (event != null && mounted) { setState(() { user = event; @@ -200,7 +201,6 @@ class _ProfilePageState extends State { TextButton( onPressed: () async { final session = await user.multiFactor.getSession(); - final auth = FirebaseAuth.instance; await auth.verifyPhoneNumber( multiFactorSession: session, phoneNumber: phoneController.text, @@ -306,7 +306,7 @@ class _ProfilePageState extends State { /// Example code for sign out. Future _signOut() async { - await FirebaseAuth.instance.signOut(); + await auth.signOut(); await GoogleSignIn().signOut(); } } diff --git a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart index 2200306a0044..35b4fcb05190 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart @@ -4,6 +4,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:html'; import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart'; import 'package:firebase_auth_web/src/firebase_auth_web_multi_factor.dart'; @@ -13,6 +14,7 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core_web/firebase_core_web.dart'; import 'package:firebase_core_web/firebase_core_web_interop.dart' as core_interop; +import 'package:flutter/foundation.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'src/firebase_auth_web_confirmation_result.dart'; @@ -89,8 +91,22 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { /// Called by PluginRegistry to register this plugin for Flutter Web static void registerWith(Registrar registrar) { - FirebaseCoreWeb.registerService('auth', () async { - await FirebaseAuthWeb.instance.delegate.onWaitInitState(); + FirebaseCoreWeb.registerService('auth', (firebaseApp) async { + final authDelegate = auth_interop.getAuthInstance(firebaseApp); + // if localhost, and emulator was previously set in localStorage, use it + if (window.location.hostname == 'localhost' && kDebugMode) { + final String? emulatorOrigin = + window.localStorage['firebaseEmulatorOrigin']; + + if (emulatorOrigin != null) { + authDelegate.useAuthEmulator(emulatorOrigin); + // ignore: avoid_print + print( + 'Using previously configured Auth emulator at $emulatorOrigin \nTo switch back to production, restart your app with the emulator turned off.', + ); + } + } + await authDelegate.onWaitInitState(); }); FirebaseAuthPlatform.instance = FirebaseAuthWeb.instance; PhoneMultiFactorGeneratorPlatform.instance = PhoneMultiFactorGeneratorWeb(); @@ -434,10 +450,24 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { @override Future useAuthEmulator(String host, int port) async { try { + // Get current local storage value + final String? emulatorOrigin = + window.localStorage['firebaseEmulatorOrigin']; + // The generic platform interface is with host and port split to // centralize logic between android/ios native, but web takes the // origin as a single string - delegate.useAuthEmulator('http://$host:$port'); + final String origin = 'http://$host:$port'; + + if (origin == emulatorOrigin) { + // If the origin is the same as the current one, do nothing + // The emulator was already started at the app start + return; + } + + delegate.useAuthEmulator(origin); + // Save to local storage so that the emulator is used on refresh + window.localStorage['firebaseEmulatorOrigin'] = origin; } catch (e) { final String code = (e as auth_interop.AuthError).code; // this catches Firebase Error from web that occurs after hot reloading & hot restarting diff --git a/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart b/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart index 4e233ebb6ec2..f663a392c243 100644 --- a/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart +++ b/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart @@ -27,7 +27,9 @@ class FirebaseWebService { }); } -typedef EnsurePluginInitialized = Future Function()?; +typedef EnsurePluginInitialized = Future Function( + firebase.App firebaseApp, +)?; /// The entry point for accessing Firebase. /// @@ -302,11 +304,11 @@ class FirebaseCoreWeb extends FirebasePlatform { _services.values.map((service) { final ensureInitializedFunction = service.ensurePluginInitialized; - if (ensureInitializedFunction == null) { + if (ensureInitializedFunction == null || app == null) { return Future.value(); } - return ensureInitializedFunction(); + return ensureInitializedFunction(app); }), ); From 2c30925fb48ac48cb95e0a5c13ef130e3070fbcb Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Mon, 6 Mar 2023 12:10:59 +0100 Subject: [PATCH 2/3] fix(auth, web): fix currentUser being null when using emulator or named instance --- packages/firebase_auth/firebase_auth/example/lib/auth.dart | 4 ++-- .../firebase_auth_web/lib/firebase_auth_web.dart | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/firebase_auth/firebase_auth/example/lib/auth.dart b/packages/firebase_auth/firebase_auth/example/lib/auth.dart index 2110b0c8c79e..19c260aeb727 100644 --- a/packages/firebase_auth/firebase_auth/example/lib/auth.dart +++ b/packages/firebase_auth/firebase_auth/example/lib/auth.dart @@ -62,10 +62,10 @@ class AuthGate extends StatefulWidget { const AuthGate({Key? key}) : super(key: key); @override - State createState() => authGateState(); + State createState() => _AuthGateState(); } -class authGateState extends State { +class _AuthGateState extends State { TextEditingController emailController = TextEditingController(); TextEditingController passwordController = TextEditingController(); TextEditingController phoneController = TextEditingController(); diff --git a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart index 35b4fcb05190..9809058ae1f3 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart @@ -467,7 +467,10 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { delegate.useAuthEmulator(origin); // Save to local storage so that the emulator is used on refresh - window.localStorage['firebaseEmulatorOrigin'] = origin; + // only in debug mode + if (kDebugMode) { + window.localStorage['firebaseEmulatorOrigin'] = origin; + } } catch (e) { final String code = (e as auth_interop.AuthError).code; // this catches Firebase Error from web that occurs after hot reloading & hot restarting From e2282d6422468dd86333bdd0768b2d23ebc8c549 Mon Sep 17 00:00:00 2001 From: Guillaume Bernos Date: Tue, 7 Mar 2023 10:15:21 +0100 Subject: [PATCH 3/3] fix(auth, web): fix currentUser being null when using emulator or named instance --- .../firebase_auth_web/lib/firebase_auth_web.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart index 9809058ae1f3..54eb1f55b363 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart @@ -96,7 +96,7 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { // if localhost, and emulator was previously set in localStorage, use it if (window.location.hostname == 'localhost' && kDebugMode) { final String? emulatorOrigin = - window.localStorage['firebaseEmulatorOrigin']; + window.sessionStorage['firebaseEmulatorOrigin']; if (emulatorOrigin != null) { authDelegate.useAuthEmulator(emulatorOrigin); @@ -450,9 +450,9 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { @override Future useAuthEmulator(String host, int port) async { try { - // Get current local storage value + // Get current session storage value final String? emulatorOrigin = - window.localStorage['firebaseEmulatorOrigin']; + window.sessionStorage['firebaseEmulatorOrigin']; // The generic platform interface is with host and port split to // centralize logic between android/ios native, but web takes the @@ -466,10 +466,10 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { } delegate.useAuthEmulator(origin); - // Save to local storage so that the emulator is used on refresh + // Save to session storage so that the emulator is used on refresh // only in debug mode if (kDebugMode) { - window.localStorage['firebaseEmulatorOrigin'] = origin; + window.sessionStorage['firebaseEmulatorOrigin'] = origin; } } catch (e) { final String code = (e as auth_interop.AuthError).code;