Skip to content

Commit ae7ebaf

Browse files
fix(firebase_auth): pass Persistence value to FirebaseAuth.instanceFor(app: app, persistence: persistence) for setting persistence on Web platform (#9138)
Co-authored-by: Mike Diarmid <mike.diarmid@gmail.com>
1 parent d8bf818 commit ae7ebaf

File tree

10 files changed

+109
-58
lines changed

10 files changed

+109
-58
lines changed

docs/auth/start.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,14 @@ On web platforms, the user's authentication state is stored in
211211
[local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
212212
If required, you can change this default behavior to only persist
213213
authentication state for the current session, or not at all. To configure these
214-
settings, call the `setPersistence()` method. (On native platforms, an
215-
[`UnimplementedError`](https://api.flutter.dev/flutter/dart-core/UnimplementedError-class.html)
216-
will be thrown.)
214+
settings, call the following method `FirebaseAuth.instanceFor(app: Firebase.app(), persistence: Persistence.LOCAL);`.
215+
You can still update the persistence for each Auth instance using `setPersistence(Persistence.NONE)`.
217216
218217
```dart
219-
// Disable persistence on web platforms
220-
await FirebaseAuth.instance.setPersistence(Persistence.NONE);
218+
// Disable persistence on web platforms. Must be called on initialization:
219+
final auth = FirebaseAuth.instanceFor(app: Firebase.app(), persistence: Persistence.NONE);
220+
// To change it after initialization, use `setPersistence()`:
221+
await auth.setPersistence(Persistence.LOCAL);
221222
```
222223
223224
## Next Steps

packages/firebase_auth/firebase_auth/example/lib/main.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ bool shouldUseFirebaseEmulator = false;
1414
// e.g via `melos run firebase:emulator`.
1515
Future<void> main() async {
1616
WidgetsFlutterBinding.ensureInitialized();
17-
1817
// We're using the manual installation on non-web platforms since Google sign in plugin doesn't yet support Dart initialization.
1918
// See related issue: https://github.com/flutter/flutter/issues/96391
2019
if (!kIsWeb) {

packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ class FirebaseAuth extends FirebasePluginPlatform {
1515
// instance with the default app before a user specifies an app.
1616
FirebaseAuthPlatform? _delegatePackingProperty;
1717

18+
// To set "persistence" on web, it is now required on the v9.0.0 or above Firebase JS SDK to pass the value on calling `initializeAuth()`.
19+
/// https://firebase.google.com/docs/reference/js/auth.md#initializeauth
20+
Persistence? _persistence;
21+
1822
/// Returns the underlying delegate implementation.
1923
///
2024
/// If called and no [_delegatePackingProperty] exists, it will first be
@@ -23,15 +27,17 @@ class FirebaseAuth extends FirebasePluginPlatform {
2327
_delegatePackingProperty ??= FirebaseAuthPlatform.instanceFor(
2428
app: app,
2529
pluginConstants: pluginConstants,
30+
persistence: _persistence,
2631
);
2732
return _delegatePackingProperty!;
2833
}
2934

3035
/// The [FirebaseApp] for this current Auth instance.
3136
FirebaseApp app;
3237

33-
FirebaseAuth._({required this.app})
34-
: super(app.name, 'plugins.flutter.io/firebase_auth');
38+
FirebaseAuth._({required this.app, Persistence? persistence})
39+
: _persistence = persistence,
40+
super(app.name, 'plugins.flutter.io/firebase_auth');
3541

3642
/// Returns an instance using the default [FirebaseApp].
3743
static FirebaseAuth get instance {
@@ -41,9 +47,11 @@ class FirebaseAuth extends FirebasePluginPlatform {
4147
}
4248

4349
/// Returns an instance using a specified [FirebaseApp].
44-
factory FirebaseAuth.instanceFor({required FirebaseApp app}) {
50+
/// Note that persistence can only be used on Web and is not supported on other platforms.
51+
factory FirebaseAuth.instanceFor(
52+
{required FirebaseApp app, Persistence? persistence}) {
4553
return _firebaseAuthInstances.putIfAbsent(app.name, () {
46-
return FirebaseAuth._(app: app);
54+
return FirebaseAuth._(app: app, persistence: persistence);
4755
});
4856
}
4957

packages/firebase_auth/firebase_auth/test/firebase_auth_test.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,8 @@ class MockFirebaseAuth extends Mock
728728
}
729729

730730
@override
731-
FirebaseAuthPlatform delegateFor({FirebaseApp? app}) {
731+
FirebaseAuthPlatform delegateFor(
732+
{FirebaseApp? app, Persistence? persistence}) {
732733
return super.noSuchMethod(
733734
Invocation.method(#delegateFor, [], {#app: app}),
734735
returnValue: TestFirebaseAuthPlatform(),
@@ -1024,7 +1025,8 @@ class FakeFirebaseAuthPlatform extends Fake
10241025
String? tenantId;
10251026

10261027
@override
1027-
FirebaseAuthPlatform delegateFor({required FirebaseApp app}) {
1028+
FirebaseAuthPlatform delegateFor(
1029+
{required FirebaseApp app, Persistence? persistence}) {
10281030
return this;
10291031
}
10301032

@@ -1085,7 +1087,8 @@ class TestFirebaseAuthPlatform extends FirebaseAuthPlatform {
10851087
}) {}
10861088

10871089
@override
1088-
FirebaseAuthPlatform delegateFor({FirebaseApp? app}) {
1090+
FirebaseAuthPlatform delegateFor(
1091+
{FirebaseApp? app, Persistence? persistence}) {
10891092
return this;
10901093
}
10911094

packages/firebase_auth/firebase_auth/test/user_test.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,8 @@ class MockFirebaseAuth extends Mock
374374
}
375375

376376
@override
377-
FirebaseAuthPlatform delegateFor({FirebaseApp? app}) {
377+
FirebaseAuthPlatform delegateFor(
378+
{FirebaseApp? app, Persistence? persistence}) {
378379
return super.noSuchMethod(
379380
Invocation.method(#delegateFor, const [], {#app: app}),
380381
returnValue: TestFirebaseAuthPlatform(),
@@ -555,7 +556,9 @@ class TestFirebaseAuthPlatform extends FirebaseAuthPlatform {
555556
TestFirebaseAuthPlatform() : super();
556557

557558
@override
558-
FirebaseAuthPlatform delegateFor({FirebaseApp? app}) => this;
559+
FirebaseAuthPlatform delegateFor(
560+
{FirebaseApp? app, Persistence? persistence}) =>
561+
this;
559562

560563
@override
561564
FirebaseAuthPlatform setInitialValues({

packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ class MethodChannelFirebaseAuth extends FirebaseAuthPlatform {
187187
///
188188
/// Instances are cached and reused for incoming event handlers.
189189
@override
190-
FirebaseAuthPlatform delegateFor({required FirebaseApp app}) {
190+
FirebaseAuthPlatform delegateFor(
191+
{required FirebaseApp app, Persistence? persistence}) {
191192
return methodChannelFirebaseAuthInstances.putIfAbsent(app.name, () {
192193
return MethodChannelFirebaseAuth(app: app);
193194
});

packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,18 @@ abstract class FirebaseAuthPlatform extends PlatformInterface {
4848
static final Object _token = Object();
4949

5050
/// Create an instance using [app] using the existing implementation
51-
factory FirebaseAuthPlatform.instanceFor({
52-
required FirebaseApp app,
53-
required Map<dynamic, dynamic> pluginConstants,
54-
}) {
55-
return FirebaseAuthPlatform.instance.delegateFor(app: app).setInitialValues(
56-
languageCode: pluginConstants['APP_LANGUAGE_CODE'],
57-
currentUser: pluginConstants['APP_CURRENT_USER'] == null
58-
? null
59-
: Map<String, dynamic>.from(pluginConstants['APP_CURRENT_USER']));
51+
factory FirebaseAuthPlatform.instanceFor(
52+
{required FirebaseApp app,
53+
required Map<dynamic, dynamic> pluginConstants,
54+
Persistence? persistence}) {
55+
return FirebaseAuthPlatform.instance
56+
.delegateFor(app: app, persistence: persistence)
57+
.setInitialValues(
58+
languageCode: pluginConstants['APP_LANGUAGE_CODE'],
59+
currentUser: pluginConstants['APP_CURRENT_USER'] == null
60+
? null
61+
: Map<String, dynamic>.from(
62+
pluginConstants['APP_CURRENT_USER']));
6063
}
6164

6265
/// The current default [FirebaseAuthPlatform] instance.
@@ -79,7 +82,8 @@ abstract class FirebaseAuthPlatform extends PlatformInterface {
7982
/// Enables delegates to create new instances of themselves if a none default
8083
/// [FirebaseApp] instance is required by the user.
8184
@protected
82-
FirebaseAuthPlatform delegateFor({required FirebaseApp app}) {
85+
FirebaseAuthPlatform delegateFor(
86+
{required FirebaseApp app, Persistence? persistence}) {
8387
throw UnimplementedError('delegateFor() is not implemented');
8488
}
8589

packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,14 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform {
3232

3333
Completer<void> _initialized = Completer();
3434

35+
// To set "persistence" on web, it is now required on the v9.0.0 or above Firebase JS SDK to pass the value on calling `initializeAuth()`.
36+
// https://firebase.google.com/docs/reference/js/auth.md#initializeauth
37+
Persistence? _persistence;
38+
3539
/// The entry point for the [FirebaseAuthWeb] class.
36-
FirebaseAuthWeb({required FirebaseApp app}) : super(appInstance: app) {
40+
FirebaseAuthWeb({required FirebaseApp app, Persistence? persistence})
41+
: super(appInstance: app) {
42+
_persistence = persistence;
3743
// Create a app instance broadcast stream for both delegate listener events
3844
_userChangesListeners[app.name] =
3945
StreamController<UserPlatform?>.broadcast();
@@ -100,13 +106,16 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform {
100106
auth_interop.Auth? _webAuth;
101107

102108
auth_interop.Auth get delegate {
103-
return _webAuth ??=
104-
auth_interop.getAuthInstance(core_interop.app(app.name));
109+
_webAuth ??= auth_interop.getAuthInstance(core_interop.app(app.name),
110+
persistence: _persistence);
111+
112+
return _webAuth!;
105113
}
106114

107115
@override
108-
FirebaseAuthPlatform delegateFor({required FirebaseApp app}) {
109-
return FirebaseAuthWeb(app: app);
116+
FirebaseAuthPlatform delegateFor(
117+
{required FirebaseApp app, Persistence? persistence}) {
118+
return FirebaseAuthWeb(app: app, persistence: persistence);
110119
}
111120

112121
@override

packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,34 @@ import 'utils/utils.dart';
2020
export 'auth_interop.dart';
2121

2222
/// Given an AppJSImp, return the Auth instance.
23-
Auth getAuthInstance(App app) {
23+
Auth getAuthInstance(App app, {Persistence? persistence}) {
24+
if (persistence != null) {
25+
auth_interop.Persistence setPersistence;
26+
switch (persistence) {
27+
case Persistence.LOCAL:
28+
setPersistence = auth_interop.browserLocalPersistence;
29+
break;
30+
case Persistence.SESSION:
31+
setPersistence = auth_interop.browserSessionPersistence;
32+
break;
33+
case Persistence.NONE:
34+
setPersistence = auth_interop.inMemoryPersistence;
35+
break;
36+
}
37+
return Auth.getInstance(auth_interop.initializeAuth(
38+
app.jsObject,
39+
jsify({
40+
'errorMap': auth_interop.debugErrorMap,
41+
'persistence': setPersistence,
42+
'popupRedirectResolver': auth_interop.browserPopupRedirectResolver
43+
})));
44+
}
45+
// `browserLocalPersistence` is the default persistence setting.
2446
return Auth.getInstance(auth_interop.initializeAuth(
2547
app.jsObject,
2648
jsify({
2749
'errorMap': auth_interop.debugErrorMap,
50+
'persistence': auth_interop.browserLocalPersistence,
2851
'popupRedirectResolver': auth_interop.browserPopupRedirectResolver
2952
})));
3053
}
@@ -497,30 +520,6 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
497520
handleThenable(auth_interop.sendSignInLinkToEmail(
498521
jsObject, email, actionCodeSettings));
499522

500-
/// Sends a password reset e-mail to the given [email].
501-
/// To confirm password reset, use the [Auth.confirmPasswordReset].
502-
///
503-
/// The optional parameter [actionCodeSettings] is the action code settings.
504-
/// If specified, the state/continue URL will be set as the 'continueUrl'
505-
/// parameter in the password reset link.
506-
/// The default password reset landing page will use this to display
507-
/// a link to go back to the app if it is installed.
508-
///
509-
/// If the [actionCodeSettings] is not specified, no URL is appended to the
510-
/// action URL. The state URL provided must belong to a domain that is
511-
/// whitelisted by the developer in the console. Otherwise an error will be
512-
/// thrown.
513-
///
514-
/// Mobile app redirects will only be applicable if the developer configures
515-
/// and accepts the Firebase Dynamic Links terms of condition.
516-
///
517-
/// The Android package name and iOS bundle ID will be respected only if
518-
/// they are configured in the same Firebase Auth project used.
519-
Future sendPasswordResetEmail(String email,
520-
[auth_interop.ActionCodeSettings? actionCodeSettings]) =>
521-
handleThenable(auth_interop.sendPasswordResetEmail(
522-
jsObject, email, actionCodeSettings));
523-
524523
/// Changes the current type of persistence on the current Auth instance for
525524
/// the currently saved Auth session and applies this type of persistence
526525
/// for future sign-in requests, including sign-in with redirect requests.
@@ -554,6 +553,30 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
554553
return handleThenable(auth_interop.setPersistence(jsObject, instance));
555554
}
556555

556+
/// Sends a password reset e-mail to the given [email].
557+
/// To confirm password reset, use the [Auth.confirmPasswordReset].
558+
///
559+
/// The optional parameter [actionCodeSettings] is the action code settings.
560+
/// If specified, the state/continue URL will be set as the 'continueUrl'
561+
/// parameter in the password reset link.
562+
/// The default password reset landing page will use this to display
563+
/// a link to go back to the app if it is installed.
564+
///
565+
/// If the [actionCodeSettings] is not specified, no URL is appended to the
566+
/// action URL. The state URL provided must belong to a domain that is
567+
/// whitelisted by the developer in the console. Otherwise an error will be
568+
/// thrown.
569+
///
570+
/// Mobile app redirects will only be applicable if the developer configures
571+
/// and accepts the Firebase Dynamic Links terms of condition.
572+
///
573+
/// The Android package name and iOS bundle ID will be respected only if
574+
/// they are configured in the same Firebase Auth project used.
575+
Future sendPasswordResetEmail(String email,
576+
[auth_interop.ActionCodeSettings? actionCodeSettings]) =>
577+
handleThenable(auth_interop.sendPasswordResetEmail(
578+
jsObject, email, actionCodeSettings));
579+
557580
/// Asynchronously signs in with the given credentials, and returns any
558581
/// available additional user information, such as user name.
559582
Future<UserCredential> signInWithCredential(

tests/test_driver/firebase_auth/firebase_auth_instance_e2e.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ void setupTests() {
481481
group('setPersistence()', () {
482482
test(
483483
'throw an unimplemented error',
484-
() async {
484+
() async {
485485
try {
486486
await FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
487487
fail('Should have thrown');
@@ -494,7 +494,7 @@ void setupTests() {
494494

495495
test(
496496
'should set persistence',
497-
() async {
497+
() async {
498498
try {
499499
await FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
500500
} catch (e) {

0 commit comments

Comments
 (0)