Skip to content

Commit

Permalink
feat(ui): Add support for configuring authentication providers global…
Browse files Browse the repository at this point in the history
…ly (additionally fixes #7801) (#8120)

Co-authored-by: Mike Diarmid <mike.diarmid@gmail.com>
  • Loading branch information
lesnitsky and Salakar committed Feb 24, 2022
1 parent c0626b1 commit ebde7d2
Show file tree
Hide file tree
Showing 20 changed files with 504 additions and 140 deletions.
59 changes: 59 additions & 0 deletions docs/ui/auth/integrating-your-first-screen.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,65 @@ return ProfileScreen(
style={{ maxHeight: 700 }}
/>

## Configuring auth providers globally

Instead of passing a list of providers to each screen, you can alternatively provide a list of provider configurations to the `FlutterFireUIAuth.configureProviders` method:

```dart
Future<void> main() async {
// Firebase app should be initialized before calling configureProviders
await Firebase.initializeApp();
FlutterFireUIAuth.configureProviders([
const EmailProviderConfiguration(),
const PhoneProviderConfiguration(),
const GoogleProviderConfiguration(clientId: GOOGLE_CLIENT_ID),
const AppleProviderConfiguration(),
const FacebookProviderConfiguration(clientId: FACEBOOK_CLIENT_ID),
const TwitterProviderConfiguration(
apiKey: TWITTER_API_KEY,
apiSecretKey: TWITTER_API_SECRET_KEY,
redirectUri: TWITTER_REDIRECT_URI,
),
]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final auth = FirebaseAuth.instance;
return MaterialApp(
initialRoute: auth.currentUser == null ? '/' : '/profile',
routes: {
'/': (context) {
return SignInScreen(
// no providerConfigs property - global configuration will be used instead
actions: [
AuthStateChangeAction<SignedIn>((context, state) {
Navigator.pushReplacementNamed(context, '/profile');
}),
],
);
},
'/profile': (context) {
return ProfileScreen(
// no providerConfigs property here as well
actions: [
SignedOutAction((context) {
Navigator.pushReplacementNamed(context, '/');
}),
],
);
},
},
);
}
}
```

## Next Steps

With your first screen integrated, you can now start adding more providers and integrating more screens!
Expand Down
53 changes: 26 additions & 27 deletions packages/flutterfire_ui/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,35 @@ import 'init.dart'
import 'config.dart';
import 'decorations.dart';

final emailLinkProviderConfig = EmailLinkProviderConfiguration(
actionCodeSettings: ActionCodeSettings(
url: 'https://reactnativefirebase.page.link',
handleCodeInApp: true,
androidMinimumVersion: '12',
androidPackageName:
'io.flutter.plugins.flutterfire_ui.flutterfire_ui_example',
iOSBundleId: 'io.flutter.plugins.flutterfireui.flutterfireUIExample',
),
);

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeFirebase();

FlutterFireUIAuth.configureProviders([
const EmailProviderConfiguration(),
emailLinkProviderConfig,
const PhoneProviderConfiguration(),
const GoogleProviderConfiguration(clientId: GOOGLE_CLIENT_ID),
const AppleProviderConfiguration(),
const FacebookProviderConfiguration(clientId: FACEBOOK_CLIENT_ID),
const TwitterProviderConfiguration(
apiKey: TWITTER_API_KEY,
apiSecretKey: TWITTER_API_SECRET_KEY,
redirectUri: TWITTER_REDIRECT_URI,
),
]);

runApp(FirebaseAuthUIExample());
}

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

final emailLinkProviderConfig = EmailLinkProviderConfiguration(
actionCodeSettings: ActionCodeSettings(
url: 'https://reactnativefirebase.page.link',
handleCodeInApp: true,
androidMinimumVersion: '12',
androidPackageName:
'io.flutter.plugins.flutterfire_ui.flutterfire_ui_example',
iOSBundleId: 'io.flutter.plugins.flutterfireui.flutterfireUIExample',
),
);

final providerConfigs = [
const EmailProviderConfiguration(),
emailLinkProviderConfig,
const PhoneProviderConfiguration(),
const GoogleProviderConfiguration(clientId: GOOGLE_CLIENT_ID),
const AppleProviderConfiguration(),
const FacebookProviderConfiguration(clientId: FACEBOOK_CLIENT_ID),
const TwitterProviderConfiguration(
apiKey: TWITTER_API_KEY,
apiSecretKey: TWITTER_API_SECRET_KEY,
redirectUri: TWITTER_REDIRECT_URI,
),
];

class FirebaseAuthUIExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -111,7 +112,6 @@ class FirebaseAuthUIExample extends StatelessWidget {
),
);
},
providerConfigs: providerConfigs,
);
},
'/phone': (context) {
Expand Down Expand Up @@ -150,7 +150,6 @@ class FirebaseAuthUIExample extends StatelessWidget {
},
'/profile': (context) {
return ProfileScreen(
providerConfigs: providerConfigs,
actions: [
SignedOutAction((context) {
Navigator.pushReplacementNamed(context, '/');
Expand Down
65 changes: 65 additions & 0 deletions packages/flutterfire_ui/lib/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,68 @@ export 'src/auth/configs/phone_provider_configuration.dart';
export 'src/auth/configs/oauth_provider_configuration.dart';
export 'src/auth/configs/email_link_provider_configuration.dart';
export 'src/auth/configs/provider_configuration.dart';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/widgets.dart';
import 'package:flutterfire_ui/src/auth/actions.dart';
import 'package:flutterfire_ui/src/auth/configs/oauth_provider_configuration.dart';
import 'package:flutterfire_ui/src/auth/oauth/oauth_providers.dart';

import 'auth.dart' show ProviderConfiguration;

class FlutterFireUIAuth {
static final _configs = <FirebaseApp, List<ProviderConfiguration>>{};
static final _configuredApps = <FirebaseApp, bool>{};

static List<ProviderConfiguration> configsFor(FirebaseApp app) {
return _configs[app] ?? [];
}

static bool isAppConfigured(FirebaseApp app) {
return _configs.containsKey(app);
}

static void configureProviders(
List<ProviderConfiguration> configs, {
FirebaseApp? app,
}) {
if (Firebase.apps.isEmpty) {
throw Exception(
'You must call Firebase.initializeApp() '
'before calling configureProviders()',
);
}

final _app = app ?? Firebase.app();

if (_configuredApps[_app] ?? false) {
throw Exception(
'You can only configure providers once '
'for each Firebase App',
);
}

_configs[_app] = configs;

configs.whereType<OAuthProviderConfiguration>().forEach((element) {
final provider = element.createProvider();
final auth = FirebaseAuth.instanceFor(app: _app);
OAuthProviders.register(auth, provider);
});
}

static Future<void> signOut({
BuildContext? context,
FirebaseAuth? auth,
}) async {
final _auth = auth ?? FirebaseAuth.instance;
await OAuthProviders.signOut(_auth);
await _auth.signOut();

if (context != null) {
final action = FlutterFireUIAction.ofType<SignedOutAction>(context);
action?.callback(context);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import '../widgets/internal/oauth_provider_button_style.dart';

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

abstract class OAuthProviderConfiguration extends ProviderConfiguration {
abstract class OAuthProviderConfiguration<T extends OAuthProvider>
extends ProviderConfiguration {
const OAuthProviderConfiguration();

Type get providerType => T;

String get defaultRedirectUri =>
'https://${Firebase.apps.first.options.projectId}.firebaseapp.com/__/auth/handler';

Expand All @@ -27,7 +30,7 @@ abstract class OAuthProviderConfiguration extends ProviderConfiguration {
);
}

OAuthProvider createProvider();
T createProvider();

String getLabel(FlutterFireUILocalizationLabels labels);
}
7 changes: 6 additions & 1 deletion packages/flutterfire_ui/lib/src/auth/flows/oauth_flow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ class OAuthFlow extends AuthFlow implements OAuthController {

@override
Future<void> signInWithProvider(TargetPlatform platform) async {
final provider = config.createProvider();
OAuthProvider? provider = OAuthProviders.resolve(auth, config.providerType);

if (provider == null) {
provider = config.createProvider();
OAuthProviders.register(auth, provider);
}

try {
value = const SigningIn();
Expand Down
52 changes: 49 additions & 3 deletions packages/flutterfire_ui/lib/src/auth/oauth/oauth_providers.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,53 @@
import 'package:desktop_webview_auth/desktop_webview_auth.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/widgets.dart';

@immutable
class ProviderKey {
final FirebaseAuth auth;
final Type providerType;

ProviderKey(this.auth, this.providerType);

@override
int get hashCode => hashValues(auth, providerType);

@override
bool operator ==(Object other) {
return hashCode == other.hashCode;
}
}

abstract class OAuthProviders {
static final _providers = <ProviderKey, OAuthProvider>{};

static void register(FirebaseAuth? auth, OAuthProvider provider) {
final _auth = auth ?? FirebaseAuth.instance;
final key = ProviderKey(_auth, provider.runtimeType);

_providers[key] = provider;
}

static OAuthProvider? resolve(FirebaseAuth? auth, Type providerType) {
final _auth = auth ?? FirebaseAuth.instance;
final key = ProviderKey(_auth, providerType);
return _providers[key];
}

static Iterable<OAuthProvider> providersFor(FirebaseAuth auth) sync* {
for (final k in _providers.keys) {
if (k.auth == auth) {
yield _providers[k]!;
}
}
}

static Future<void> signOut([FirebaseAuth? auth]) async {
final _auth = auth ?? FirebaseAuth.instance;
final futures = providersFor(_auth).map((e) => e.signOut());
await Future.wait(futures);
}
}

abstract class OAuthProvider {
Future<OAuthCredential> signIn();
Expand All @@ -19,9 +67,7 @@ abstract class OAuthProvider {
return credential;
}

Future<void> signOut() async {
await FirebaseAuth.instance.signOut();
}
Future<void> signOut() async {}
}

extension OAuthHelpers on User {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ String sha256ofString(String input) {
return digest.toString();
}

class AppleProviderImpl extends OAuthProvider {
abstract class AppleProvider extends OAuthProvider {}

class AppleProviderImpl extends AppleProvider {
@override
Future<fba.OAuthCredential> signIn() async {
final rawNonce = generateNonce();
Expand Down Expand Up @@ -72,11 +74,12 @@ class AppleProviderImpl extends OAuthProvider {
dynamic get firebaseAuthProvider => null;
}

class AppleProviderConfiguration extends OAuthProviderConfiguration {
class AppleProviderConfiguration
extends OAuthProviderConfiguration<AppleProvider> {
const AppleProviderConfiguration();

@override
OAuthProvider createProvider() {
AppleProvider createProvider() {
return AppleProviderImpl();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import '../../oauth/provider_resolvers.dart';
import '../../auth_flow.dart';
import '../oauth_providers.dart';

class FacebookProviderImpl extends OAuthProvider {
abstract class FacebookProvider extends OAuthProvider {}

class FacebookProviderImpl extends FacebookProvider {
final _provider = FacebookAuth.instance;
final String clientId;
final String redirectUri;
Expand Down Expand Up @@ -47,6 +49,11 @@ class FacebookProviderImpl extends OAuthProvider {
}
}

@override
Future<void> signOut() async {
await _provider.logOut();
}

@override
OAuthCredential fromDesktopAuthResult(AuthResult result) {
return FacebookAuthProvider.credential(result.accessToken!);
Expand All @@ -56,7 +63,8 @@ class FacebookProviderImpl extends OAuthProvider {
FacebookAuthProvider get firebaseAuthProvider => FacebookAuthProvider();
}

class FacebookProviderConfiguration extends OAuthProviderConfiguration {
class FacebookProviderConfiguration
extends OAuthProviderConfiguration<FacebookProvider> {
final String clientId;
final String? redirectUri;

Expand All @@ -65,7 +73,7 @@ class FacebookProviderConfiguration extends OAuthProviderConfiguration {
this.redirectUri,
});

OAuthProvider get _provider => FacebookProviderImpl(
FacebookProvider get _provider => FacebookProviderImpl(
clientId: clientId,
redirectUri: redirectUri ?? defaultRedirectUri,
);
Expand All @@ -74,7 +82,7 @@ class FacebookProviderConfiguration extends OAuthProviderConfiguration {
String get providerId => FACEBOOK_PROVIDER_ID;

@override
OAuthProvider createProvider() {
FacebookProvider createProvider() {
return _provider;
}

Expand Down

0 comments on commit ebde7d2

Please sign in to comment.