firebase / flutterfire

🔥 A collection of Firebase plugins for Flutter apps.
https://firebase.google.com/docs/flutter/setup
BSD 3-Clause "New" or "Revised" License
8.51k stars 3.92k forks source link

🐛 [firebase_auth]Single Sign-Out Issue with FirebaseAuth and SAML IdP #11294

Open motucraft opened 1 year ago

motucraft commented 1 year ago

Bug report

Describe the bug FirebaseAuth in Flutter allows for SAML authentication, but there seems to be no clear way to handle Single Sign-Out. When I execute FirebaseAuth.instance.signOut(), it only signs the user out of Firebase, leaving the session in the Identity Provider (IdP) still active.

Steps to reproduce

Steps to reproduce the behavior:

  1. Authenticate using FirebaseAuth with SAML as IdP
  2. Execute FirebaseAuth.instance.signOut()
  3. Attempt to re-authenticate. The user will be re-authenticated without being asked for credentials due to the active session in the IdP.

Expected behavior

I would expect a way to sign out from both Firebase and the Identity Provider to achieve a complete Single Sign-Out.

Sample project

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:simple_logger/simple_logger.dart';

import 'firebase_options.dart';

part 'main.g.dart';

late final FirebaseApp firebaseApp;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  firebaseApp = await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  await FirebaseAuth.instance.userChanges().first;

  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firebase SAML Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const SAMLSample(),
    );
  }
}

class SAMLSample extends ConsumerWidget {
  const SAMLSample({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final authController = ref.watch(authControllerProvider);
    return authController.when(
      data: (data) {
        return Scaffold(
          body: SafeArea(
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  if (data == null)
                    ElevatedButton(
                      onPressed: () async => await ref
                          .read(authControllerProvider.notifier)
                          .samlSignIn(),
                      child: const Text('SAML Sign In'),
                    ),
                  if (data != null) ...[
                    const Text('Firebase SAML Sign-in SUCCESS!!!',
                        style: TextStyle(fontSize: 18)),
                    const SizedBox(height: 8),
                    SelectableText('Email: ${data.email!}',
                        style: const TextStyle(fontSize: 18)),
                    const SizedBox(height: 8),
                    SelectableText('Uid: ${data.uid}',
                        style: const TextStyle(fontSize: 18)),
                    const SizedBox(height: 24),
                    ElevatedButton(
                      onPressed: () async => await ref
                          .read(authControllerProvider.notifier)
                          .samlSignOut(),
                      child: const Text('Sign Out'),
                    ),
                  ]
                ],
              ),
            ),
          ),
        );
      },
      error: (error, stack) {
        logger.severe(error);
        logger.severe(stack);
        return Scaffold(
          body: Text(
            error.toString(),
            style: const TextStyle(color: Colors.red),
          ),
        );
      },
      loading: () => const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      ),
    );
  }
}

@riverpod
SAMLAuthProvider samlAuth(SamlAuthRef ref) {
  return SAMLAuthProvider('saml.saml-provider');
}

@riverpod
class AuthController extends _$AuthController {
  final _auth = FirebaseAuth.instance;

  @override
  FutureOr<User?> build() async {
    _auth.userChanges().listen((user) {
      state = AsyncValue.data(user);
    });

    return state.value;
  }

  Future<void> samlSignIn() async {
    state = const AsyncLoading<User?>().copyWithPrevious(state);
    state = await AsyncValue.guard(() async {
      final userCredential = await FirebaseAuth.instanceFor(app: firebaseApp)
          .signInWithPopup(ref.read(samlAuthProvider));
      return userCredential.user;
    });
  }

  Future<void> samlSignOut() async {
    state = const AsyncLoading<User?>().copyWithPrevious(state);
    state = await AsyncValue.guard(() async {
      await FirebaseAuth.instance.signOut();
      return null;
    });
  }
}

final logger = SimpleLogger()
  ..setLevel(
    Level.ALL,
    includeCallerInfo: true,
  );
pubspec.yaml ```yaml name: firebase_saml description: A new Flutter project. publish_to: 'none' version: 1.0.0+1 environment: sdk: '>=3.0.6 <4.0.0' dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.5 firebase_core: ^2.15.0 firebase_auth: ^4.7.0 hooks_riverpod: ^2.3.6 flutter_hooks: ^0.18.6 simple_logger: ^1.9.0+2 riverpod_annotation: ^2.1.1 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.2 riverpod_generator: ^2.2.3 build_runner: ^2.4.6 custom_lint: ^0.4.0 riverpod_lint: ^1.3.2 flutter: uses-material-design: true ```

Additional context

I am trying to achieve a Single Sign-Out solution that allows the user to be completely signed out from both Firebase and the SAML Identity Provider. This seems to be essential for a proper Single Sign-Out implementation. Please advise on how this can be achieved or consider it as a feature request for firebase_auth package.


Flutter doctor

Run flutter doctor and paste the output below:

Click To Expand ``` % flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.10.6, on macOS 13.4.1 22F770820d darwin-arm64, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2) [✓] Xcode - develop for iOS and macOS (Xcode 14.3.1) [✓] Chrome - develop for the web [✓] Android Studio (version 2022.2) [✓] Android Studio (version 2022.2) [✓] IntelliJ IDEA Ultimate Edition (version 2023.1.4) [✓] IntelliJ IDEA Ultimate Edition (version 2023.1.3) [✓] IntelliJ IDEA Ultimate Edition (version 2023.1.4) [✓] VS Code (version 1.80.0) [✓] Connected device (3 available) [✓] Network resources • No issues found! ```

Flutter dependencies

Run flutter pub deps -- --style=compact and paste the output below:

Click To Expand ``` % flutter pub deps -- --style=compact Dart SDK 3.0.6 Flutter SDK 3.10.6 firebase_saml 1.0.0+1 dependencies: - cupertino_icons 1.0.5 - firebase_auth 4.7.0 [firebase_auth_platform_interface firebase_auth_web firebase_core firebase_core_platform_interface flutter meta] - firebase_core 2.15.0 [firebase_core_platform_interface firebase_core_web flutter meta] - flutter 0.0.0 [characters collection js material_color_utilities meta vector_math sky_engine] - flutter_hooks 0.18.6 [flutter] - hooks_riverpod 2.3.6 [collection flutter flutter_hooks flutter_riverpod riverpod state_notifier] - riverpod_annotation 2.1.1 [meta riverpod] - simple_logger 1.9.0+2 [logging stack_trace] dev dependencies: - build_runner 2.4.6 [analyzer args async build build_config build_daemon build_resolvers build_runner_core code_builder collection crypto dart_style frontend_server_client glob graphs http_multi_server io js logging meta mime package_config path pool pub_semver pubspec_parse shelf shelf_web_socket stack_trace stream_transform timing watcher web_socket_channel yaml] - custom_lint 0.4.0 [analyzer analyzer_plugin args async ci cli_util collection freezed_annotation json_annotation meta package_config path pub_semver pubspec_parse rxdart uuid yaml] - flutter_lints 2.0.2 [lints] - flutter_test 0.0.0 [flutter test_api path fake_async clock stack_trace vector_math async boolean_selector characters collection js matcher material_color_utilities meta source_span stream_channel string_scanner term_glyph] - riverpod_generator 2.2.3 [analyzer build build_config collection crypto meta path riverpod_analyzer_utils riverpod_annotation source_gen] - riverpod_lint 1.3.2 [analyzer analyzer_plugin collection custom_lint_builder meta path riverpod riverpod_analyzer_utils source_span yaml] transitive dependencies: - _fe_analyzer_shared 60.0.0 [meta] - _flutterfire_internals 1.3.4 [collection firebase_core firebase_core_platform_interface flutter meta] - analyzer 5.12.0 [_fe_analyzer_shared collection convert crypto glob meta package_config path pub_semver source_span watcher yaml] - analyzer_plugin 0.11.2 [analyzer collection dart_style pub_semver yaml] - args 2.4.2 - async 2.11.0 [collection meta] - boolean_selector 2.1.1 [source_span string_scanner] - build 2.4.1 [analyzer async convert crypto glob logging meta package_config path] - build_config 1.1.1 [checked_yaml json_annotation path pubspec_parse yaml] - build_daemon 4.0.0 [built_collection built_value http_multi_server logging path pool shelf shelf_web_socket stream_transform watcher web_socket_channel] - build_resolvers 2.2.1 [analyzer async build collection crypto graphs logging package_config path pool pub_semver stream_transform yaml] - build_runner_core 7.2.10 [async build build_config build_resolvers collection convert crypto glob graphs json_annotation logging meta package_config path pool timing watcher yaml] - built_collection 5.1.1 - built_value 8.6.1 [built_collection collection fixnum meta] - characters 1.3.0 - checked_yaml 2.0.3 [json_annotation source_span yaml] - ci 0.1.0 - cli_util 0.4.0 [meta path] - clock 1.1.1 - code_builder 4.5.0 [built_collection built_value collection matcher meta] - collection 1.17.1 - convert 3.1.1 [typed_data] - crypto 3.0.3 [typed_data] - custom_lint_builder 0.4.0 [analyzer analyzer_plugin collection custom_lint custom_lint_core glob hotreloader meta path pubspec_parse rxdart] - custom_lint_core 0.4.0 [analyzer analyzer_plugin collection custom_lint matcher meta path pubspec_parse source_span yaml] - dart_style 2.3.2 [analyzer args path pub_semver source_span] - fake_async 1.3.1 [clock collection] - file 7.0.0 [meta path] - firebase_auth_platform_interface 6.16.0 [_flutterfire_internals collection firebase_core flutter meta plugin_platform_interface] - firebase_auth_web 5.6.0 [firebase_auth_platform_interface firebase_core firebase_core_web flutter flutter_web_plugins http_parser js meta] - firebase_core_platform_interface 4.8.0 [collection flutter flutter_test meta plugin_platform_interface] - firebase_core_web 2.6.0 [firebase_core_platform_interface flutter flutter_web_plugins js meta] - fixnum 1.1.0 - flutter_riverpod 2.3.6 [collection flutter meta riverpod state_notifier] - flutter_web_plugins 0.0.0 [flutter js characters collection material_color_utilities meta vector_math] - freezed_annotation 2.4.1 [collection json_annotation meta] - frontend_server_client 3.2.0 [async path] - glob 2.1.2 [async collection file path string_scanner] - graphs 2.3.1 [collection] - hotreloader 3.0.6 [collection logging path stream_transform vm_service watcher] - http_multi_server 3.2.1 [async] - http_parser 4.0.2 [collection source_span string_scanner typed_data] - io 1.0.4 [meta path string_scanner] - js 0.6.7 [meta] - json_annotation 4.8.1 [meta] - lints 2.1.1 - logging 1.2.0 - matcher 0.12.15 [async meta stack_trace term_glyph test_api] - material_color_utilities 0.2.0 - meta 1.9.1 - mime 1.0.4 - package_config 2.1.0 [path] - path 1.8.3 - plugin_platform_interface 2.1.4 [meta] - pool 1.5.1 [async stack_trace] - pub_semver 2.1.4 [collection meta] - pubspec_parse 1.2.3 [checked_yaml collection json_annotation pub_semver yaml] - riverpod 2.3.6 [meta stack_trace state_notifier] - riverpod_analyzer_utils 0.3.1 [analyzer collection crypto custom_lint_core freezed_annotation meta path source_span] - rxdart 0.27.7 - shelf 1.4.1 [async collection http_parser path stack_trace stream_channel] - shelf_web_socket 1.0.4 [shelf stream_channel web_socket_channel] - sky_engine 0.0.99 - source_gen 1.4.0 [analyzer async build dart_style glob path source_span yaml] - source_span 1.9.1 [collection path term_glyph] - stack_trace 1.11.0 [path] - state_notifier 0.7.2+1 [meta] - stream_channel 2.1.1 [async] - stream_transform 2.1.0 - string_scanner 1.2.0 [source_span] - term_glyph 1.2.1 - test_api 0.5.1 [async boolean_selector collection meta source_span stack_trace stream_channel string_scanner term_glyph matcher] - timing 1.0.1 [json_annotation] - typed_data 1.3.2 [collection] - uuid 3.0.7 [crypto] - vector_math 2.1.4 - vm_service 11.7.2 - watcher 1.1.0 [async path] - web_socket_channel 2.4.0 [async crypto stream_channel] - yaml 3.1.2 [collection source_span string_scanner] ```

darshankawar commented 1 year ago

@motucraft It looks it may not be supported, but I am not completely sure, but check below and see if they help or not.

https://stackoverflow.com/questions/69443157/is-it-possible-to-implement-saml-and-firebase-for-mobile-device-in-flutter

https://medium.com/@tfalvo/single-sign-on-sso-for-your-firebase-app-with-saml-f67c71e0b4d6

motucraft commented 1 year ago

I also suspect that the Identity Platform itself may not support this in the first place. However, isn't the inability to invalidate a session through sign-out a critical issue?

Is there anything we as users can do about this?

darshankawar commented 12 months ago

@motucraft Since flutterfire plugins are thin wrappers around native Firebase sdks, you might want to check if the native firebase sdks supports it or not. If not, I guess the implementation would probably need to come from them so that flutterfire plugins can leverage it.

google-oss-bot commented 11 months ago

Hey @motucraft. We need more information to resolve this issue but there hasn't been an update in 7 weekdays. I'm marking the issue as stale and if there are no new updates in the next 7 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

motucraft commented 11 months ago

Thank you for your suggestion. Since I am working with Flutter Web, I suppose the JavaScript Firebase SDK is relevant to my issue. Should I direct my inquiry towards the maintainers of the JavaScript Firebase SDK? Your guidance is appreciated.

darshankawar commented 11 months ago

Sorry for late reply @motucraft . I looked into the SAML implementation for the firebase_auth plugin and the support was added as part of https://github.com/firebase/flutterfire/pull/10075. Coming to your original issue that there's no clear way to handle Single Sign-Out, I think it is better to keep this issue open here and I'll label it for team's input on expected behavior and if there's any fix needed.

darshankawar commented 11 months ago

/cc @Lyokone for thoughts.

motucraft commented 5 months ago

Almost half a year has passed.