Skip to content

Commit ebde7d2

Browse files
lesnitskySalakar
andauthored
feat(ui): Add support for configuring authentication providers globally (additionally fixes #7801) (#8120)
Co-authored-by: Mike Diarmid <[email protected]>
1 parent c0626b1 commit ebde7d2

20 files changed

+504
-140
lines changed

docs/ui/auth/integrating-your-first-screen.mdx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,65 @@ return ProfileScreen(
492492
style={{ maxHeight: 700 }}
493493
/>
494494

495+
## Configuring auth providers globally
496+
497+
Instead of passing a list of providers to each screen, you can alternatively provide a list of provider configurations to the `FlutterFireUIAuth.configureProviders` method:
498+
499+
```dart
500+
Future<void> main() async {
501+
// Firebase app should be initialized before calling configureProviders
502+
await Firebase.initializeApp();
503+
504+
FlutterFireUIAuth.configureProviders([
505+
const EmailProviderConfiguration(),
506+
const PhoneProviderConfiguration(),
507+
const GoogleProviderConfiguration(clientId: GOOGLE_CLIENT_ID),
508+
const AppleProviderConfiguration(),
509+
const FacebookProviderConfiguration(clientId: FACEBOOK_CLIENT_ID),
510+
const TwitterProviderConfiguration(
511+
apiKey: TWITTER_API_KEY,
512+
apiSecretKey: TWITTER_API_SECRET_KEY,
513+
redirectUri: TWITTER_REDIRECT_URI,
514+
),
515+
]);
516+
517+
runApp(MyApp());
518+
}
519+
520+
class MyApp extends StatelessWidget {
521+
@override
522+
Widget build(BuildContext context) {
523+
final auth = FirebaseAuth.instance;
524+
525+
return MaterialApp(
526+
initialRoute: auth.currentUser == null ? '/' : '/profile',
527+
routes: {
528+
'/': (context) {
529+
return SignInScreen(
530+
// no providerConfigs property - global configuration will be used instead
531+
actions: [
532+
AuthStateChangeAction<SignedIn>((context, state) {
533+
Navigator.pushReplacementNamed(context, '/profile');
534+
}),
535+
],
536+
);
537+
},
538+
'/profile': (context) {
539+
return ProfileScreen(
540+
// no providerConfigs property here as well
541+
actions: [
542+
SignedOutAction((context) {
543+
Navigator.pushReplacementNamed(context, '/');
544+
}),
545+
],
546+
);
547+
},
548+
},
549+
);
550+
}
551+
}
552+
```
553+
495554
## Next Steps
496555

497556
With your first screen integrated, you can now start adding more providers and integrating more screens!

packages/flutterfire_ui/example/lib/main.dart

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,35 @@ import 'init.dart'
1010
import 'config.dart';
1111
import 'decorations.dart';
1212

13+
final emailLinkProviderConfig = EmailLinkProviderConfiguration(
14+
actionCodeSettings: ActionCodeSettings(
15+
url: 'https://reactnativefirebase.page.link',
16+
handleCodeInApp: true,
17+
androidMinimumVersion: '12',
18+
androidPackageName:
19+
'io.flutter.plugins.flutterfire_ui.flutterfire_ui_example',
20+
iOSBundleId: 'io.flutter.plugins.flutterfireui.flutterfireUIExample',
21+
),
22+
);
23+
1324
Future<void> main() async {
1425
WidgetsFlutterBinding.ensureInitialized();
1526
await initializeFirebase();
27+
28+
FlutterFireUIAuth.configureProviders([
29+
const EmailProviderConfiguration(),
30+
emailLinkProviderConfig,
31+
const PhoneProviderConfiguration(),
32+
const GoogleProviderConfiguration(clientId: GOOGLE_CLIENT_ID),
33+
const AppleProviderConfiguration(),
34+
const FacebookProviderConfiguration(clientId: FACEBOOK_CLIENT_ID),
35+
const TwitterProviderConfiguration(
36+
apiKey: TWITTER_API_KEY,
37+
apiSecretKey: TWITTER_API_SECRET_KEY,
38+
redirectUri: TWITTER_REDIRECT_URI,
39+
),
40+
]);
41+
1642
runApp(FirebaseAuthUIExample());
1743
}
1844

@@ -26,31 +52,6 @@ class LabelOverrides extends DefaultLocalizations {
2652
String get emailInputLabel => 'Enter your email';
2753
}
2854

29-
final emailLinkProviderConfig = EmailLinkProviderConfiguration(
30-
actionCodeSettings: ActionCodeSettings(
31-
url: 'https://reactnativefirebase.page.link',
32-
handleCodeInApp: true,
33-
androidMinimumVersion: '12',
34-
androidPackageName:
35-
'io.flutter.plugins.flutterfire_ui.flutterfire_ui_example',
36-
iOSBundleId: 'io.flutter.plugins.flutterfireui.flutterfireUIExample',
37-
),
38-
);
39-
40-
final providerConfigs = [
41-
const EmailProviderConfiguration(),
42-
emailLinkProviderConfig,
43-
const PhoneProviderConfiguration(),
44-
const GoogleProviderConfiguration(clientId: GOOGLE_CLIENT_ID),
45-
const AppleProviderConfiguration(),
46-
const FacebookProviderConfiguration(clientId: FACEBOOK_CLIENT_ID),
47-
const TwitterProviderConfiguration(
48-
apiKey: TWITTER_API_KEY,
49-
apiSecretKey: TWITTER_API_SECRET_KEY,
50-
redirectUri: TWITTER_REDIRECT_URI,
51-
),
52-
];
53-
5455
class FirebaseAuthUIExample extends StatelessWidget {
5556
@override
5657
Widget build(BuildContext context) {
@@ -111,7 +112,6 @@ class FirebaseAuthUIExample extends StatelessWidget {
111112
),
112113
);
113114
},
114-
providerConfigs: providerConfigs,
115115
);
116116
},
117117
'/phone': (context) {
@@ -150,7 +150,6 @@ class FirebaseAuthUIExample extends StatelessWidget {
150150
},
151151
'/profile': (context) {
152152
return ProfileScreen(
153-
providerConfigs: providerConfigs,
154153
actions: [
155154
SignedOutAction((context) {
156155
Navigator.pushReplacementNamed(context, '/');

packages/flutterfire_ui/lib/auth.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,68 @@ export 'src/auth/configs/phone_provider_configuration.dart';
9191
export 'src/auth/configs/oauth_provider_configuration.dart';
9292
export 'src/auth/configs/email_link_provider_configuration.dart';
9393
export 'src/auth/configs/provider_configuration.dart';
94+
95+
import 'package:firebase_auth/firebase_auth.dart';
96+
import 'package:firebase_core/firebase_core.dart';
97+
import 'package:flutter/widgets.dart';
98+
import 'package:flutterfire_ui/src/auth/actions.dart';
99+
import 'package:flutterfire_ui/src/auth/configs/oauth_provider_configuration.dart';
100+
import 'package:flutterfire_ui/src/auth/oauth/oauth_providers.dart';
101+
102+
import 'auth.dart' show ProviderConfiguration;
103+
104+
class FlutterFireUIAuth {
105+
static final _configs = <FirebaseApp, List<ProviderConfiguration>>{};
106+
static final _configuredApps = <FirebaseApp, bool>{};
107+
108+
static List<ProviderConfiguration> configsFor(FirebaseApp app) {
109+
return _configs[app] ?? [];
110+
}
111+
112+
static bool isAppConfigured(FirebaseApp app) {
113+
return _configs.containsKey(app);
114+
}
115+
116+
static void configureProviders(
117+
List<ProviderConfiguration> configs, {
118+
FirebaseApp? app,
119+
}) {
120+
if (Firebase.apps.isEmpty) {
121+
throw Exception(
122+
'You must call Firebase.initializeApp() '
123+
'before calling configureProviders()',
124+
);
125+
}
126+
127+
final _app = app ?? Firebase.app();
128+
129+
if (_configuredApps[_app] ?? false) {
130+
throw Exception(
131+
'You can only configure providers once '
132+
'for each Firebase App',
133+
);
134+
}
135+
136+
_configs[_app] = configs;
137+
138+
configs.whereType<OAuthProviderConfiguration>().forEach((element) {
139+
final provider = element.createProvider();
140+
final auth = FirebaseAuth.instanceFor(app: _app);
141+
OAuthProviders.register(auth, provider);
142+
});
143+
}
144+
145+
static Future<void> signOut({
146+
BuildContext? context,
147+
FirebaseAuth? auth,
148+
}) async {
149+
final _auth = auth ?? FirebaseAuth.instance;
150+
await OAuthProviders.signOut(_auth);
151+
await _auth.signOut();
152+
153+
if (context != null) {
154+
final action = FlutterFireUIAction.ofType<SignedOutAction>(context);
155+
action?.callback(context);
156+
}
157+
}
158+
}

packages/flutterfire_ui/lib/src/auth/configs/oauth_provider_configuration.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ import '../widgets/internal/oauth_provider_button_style.dart';
1010

1111
import '../flows/oauth_flow.dart';
1212

13-
abstract class OAuthProviderConfiguration extends ProviderConfiguration {
13+
abstract class OAuthProviderConfiguration<T extends OAuthProvider>
14+
extends ProviderConfiguration {
1415
const OAuthProviderConfiguration();
1516

17+
Type get providerType => T;
18+
1619
String get defaultRedirectUri =>
1720
'https://${Firebase.apps.first.options.projectId}.firebaseapp.com/__/auth/handler';
1821

@@ -27,7 +30,7 @@ abstract class OAuthProviderConfiguration extends ProviderConfiguration {
2730
);
2831
}
2932

30-
OAuthProvider createProvider();
33+
T createProvider();
3134

3235
String getLabel(FlutterFireUILocalizationLabels labels);
3336
}

packages/flutterfire_ui/lib/src/auth/flows/oauth_flow.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ class OAuthFlow extends AuthFlow implements OAuthController {
2222

2323
@override
2424
Future<void> signInWithProvider(TargetPlatform platform) async {
25-
final provider = config.createProvider();
25+
OAuthProvider? provider = OAuthProviders.resolve(auth, config.providerType);
26+
27+
if (provider == null) {
28+
provider = config.createProvider();
29+
OAuthProviders.register(auth, provider);
30+
}
2631

2732
try {
2833
value = const SigningIn();

packages/flutterfire_ui/lib/src/auth/oauth/oauth_providers.dart

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,53 @@
11
import 'package:desktop_webview_auth/desktop_webview_auth.dart';
22
import 'package:firebase_auth/firebase_auth.dart';
3+
import 'package:flutter/widgets.dart';
4+
5+
@immutable
6+
class ProviderKey {
7+
final FirebaseAuth auth;
8+
final Type providerType;
9+
10+
ProviderKey(this.auth, this.providerType);
11+
12+
@override
13+
int get hashCode => hashValues(auth, providerType);
14+
15+
@override
16+
bool operator ==(Object other) {
17+
return hashCode == other.hashCode;
18+
}
19+
}
20+
21+
abstract class OAuthProviders {
22+
static final _providers = <ProviderKey, OAuthProvider>{};
23+
24+
static void register(FirebaseAuth? auth, OAuthProvider provider) {
25+
final _auth = auth ?? FirebaseAuth.instance;
26+
final key = ProviderKey(_auth, provider.runtimeType);
27+
28+
_providers[key] = provider;
29+
}
30+
31+
static OAuthProvider? resolve(FirebaseAuth? auth, Type providerType) {
32+
final _auth = auth ?? FirebaseAuth.instance;
33+
final key = ProviderKey(_auth, providerType);
34+
return _providers[key];
35+
}
36+
37+
static Iterable<OAuthProvider> providersFor(FirebaseAuth auth) sync* {
38+
for (final k in _providers.keys) {
39+
if (k.auth == auth) {
40+
yield _providers[k]!;
41+
}
42+
}
43+
}
44+
45+
static Future<void> signOut([FirebaseAuth? auth]) async {
46+
final _auth = auth ?? FirebaseAuth.instance;
47+
final futures = providersFor(_auth).map((e) => e.signOut());
48+
await Future.wait(futures);
49+
}
50+
}
351

452
abstract class OAuthProvider {
553
Future<OAuthCredential> signIn();
@@ -19,9 +67,7 @@ abstract class OAuthProvider {
1967
return credential;
2068
}
2169

22-
Future<void> signOut() async {
23-
await FirebaseAuth.instance.signOut();
24-
}
70+
Future<void> signOut() async {}
2571
}
2672

2773
extension OAuthHelpers on User {

packages/flutterfire_ui/lib/src/auth/oauth/providers/apple_provider.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ String sha256ofString(String input) {
3131
return digest.toString();
3232
}
3333

34-
class AppleProviderImpl extends OAuthProvider {
34+
abstract class AppleProvider extends OAuthProvider {}
35+
36+
class AppleProviderImpl extends AppleProvider {
3537
@override
3638
Future<fba.OAuthCredential> signIn() async {
3739
final rawNonce = generateNonce();
@@ -72,11 +74,12 @@ class AppleProviderImpl extends OAuthProvider {
7274
dynamic get firebaseAuthProvider => null;
7375
}
7476

75-
class AppleProviderConfiguration extends OAuthProviderConfiguration {
77+
class AppleProviderConfiguration
78+
extends OAuthProviderConfiguration<AppleProvider> {
7679
const AppleProviderConfiguration();
7780

7881
@override
79-
OAuthProvider createProvider() {
82+
AppleProvider createProvider() {
8083
return AppleProviderImpl();
8184
}
8285

packages/flutterfire_ui/lib/src/auth/oauth/providers/facebook_provider.dart

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import '../../oauth/provider_resolvers.dart';
1212
import '../../auth_flow.dart';
1313
import '../oauth_providers.dart';
1414

15-
class FacebookProviderImpl extends OAuthProvider {
15+
abstract class FacebookProvider extends OAuthProvider {}
16+
17+
class FacebookProviderImpl extends FacebookProvider {
1618
final _provider = FacebookAuth.instance;
1719
final String clientId;
1820
final String redirectUri;
@@ -47,6 +49,11 @@ class FacebookProviderImpl extends OAuthProvider {
4749
}
4850
}
4951

52+
@override
53+
Future<void> signOut() async {
54+
await _provider.logOut();
55+
}
56+
5057
@override
5158
OAuthCredential fromDesktopAuthResult(AuthResult result) {
5259
return FacebookAuthProvider.credential(result.accessToken!);
@@ -56,7 +63,8 @@ class FacebookProviderImpl extends OAuthProvider {
5663
FacebookAuthProvider get firebaseAuthProvider => FacebookAuthProvider();
5764
}
5865

59-
class FacebookProviderConfiguration extends OAuthProviderConfiguration {
66+
class FacebookProviderConfiguration
67+
extends OAuthProviderConfiguration<FacebookProvider> {
6068
final String clientId;
6169
final String? redirectUri;
6270

@@ -65,7 +73,7 @@ class FacebookProviderConfiguration extends OAuthProviderConfiguration {
6573
this.redirectUri,
6674
});
6775

68-
OAuthProvider get _provider => FacebookProviderImpl(
76+
FacebookProvider get _provider => FacebookProviderImpl(
6977
clientId: clientId,
7078
redirectUri: redirectUri ?? defaultRedirectUri,
7179
);
@@ -74,7 +82,7 @@ class FacebookProviderConfiguration extends OAuthProviderConfiguration {
7482
String get providerId => FACEBOOK_PROVIDER_ID;
7583

7684
@override
77-
OAuthProvider createProvider() {
85+
FacebookProvider createProvider() {
7886
return _provider;
7987
}
8088

0 commit comments

Comments
 (0)