Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: usermanager.logout() Never completes/returns result of promise. #66

Open
jlambright opened this issue Apr 1, 2024 · 9 comments
Open
Labels
bug Something isn't working waiting for feedback

Comments

@jlambright
Copy link

jlambright commented Apr 1, 2024

Description

I'm currently implementing our logout flow, however I've noticed the following events during the execution of the process:

  1. The logout tab opens and closes almost immediately (blink and you'll miss it).
  2. When tracing the execution with breakpoints, I was able to find that the following chunk from user_manager.dart never returned/completed (I sat for minutes waiting for something, and not even an error).
   final result = await resultFuture;
    if (result == null) {
      if (kIsWeb &&
          options.web.navigationMode ==
              OidcPlatformSpecificOptions_Web_NavigationMode.samePage) {
        //wait for a result after redirect.
        return;
      }
      await forgetUser();
      return;
    }
    await _handleEndSessionResponse(result: result);
  }

  Future<void> _handleEndSessionResponse({
    required OidcEndSessionResponse result,
  }) async {
    //found result!
    final resState = result.state;
    if (resState == null) {
      await forgetUser();
      return;
    }
    final resStateData = await store.getStateData(resState);
    if (resStateData == null) {
      _logAndThrow("Didn't receive correct state value.");
    }
    final parsedState = OidcState.fromStorageString(resStateData);
    await store.setStateData(state: resState, stateData: null);
    if (parsedState is! OidcEndSessionState) {
      _logAndThrow('received wrong state type (${parsedState.runtimeType}).');
    }
    //if all state checks are successful, do logout.
    await forgetUser();
  }

image

  1. On the auth server side, we see the logout event occur.

Steps To Reproduce
I'm not quite sure how to reproduce this, as I'm hoping it's configuration issue of some kind, maybe something to do with the redirect.html???

Expected Behavior

I'd expect for the promise to be fulfilled or throw an error of some kind.

Additional Context

  • Flutter Web Project
  • Zitadel Auth OIDC Provider
  • New Tab Config
@jlambright jlambright added the bug Something isn't working label Apr 1, 2024
@jlambright
Copy link
Author

Additionally, we've added the following event monitors and we never see a pre-logout or null user event occur.

void monitorUserStream() {
    final AuthService authService = Get.find<AuthService>();
    userStream = authService.currentUser.stream;
    userStream.listen((userEvent) {
      final CommandService commandService = Get.find<CommandService>();
      if (commandService.isBusy.isTrue) {
        commandService.busyToggle();
      }
      if (userEvent == null) {
        if (Get.currentRoute != Routes.landing) {
          Get.offAllNamed(Routes.landing);
        }
      } else {
        if (Get.currentRoute == Routes.landing) {
          Get.offAllNamed(Routes.dispatch);
        }
      }
    });

    _oidcEventStream = authService.oidcEventStream();
    _oidcEventStream.listen((event) {
      switch (event) {
        case OidcPreLogoutEvent(:final currentUser):
          Get.log('Pre-Logout Event: ${event.toString()}');
          break;
        default:
          Get.log('Generic OIDC Event: ${event.toString()}');
          break;
      }
      return;
    });
  }

@ahmednfwela
Copy link
Member

Have you checked https://github.com/zitadel/zitadel_flutter ?
I just tested the login/logout flow and it works perfectly.

@jlambright
Copy link
Author

They're also using v0.4.3 of oidc. I based our login/logout flow on that example though.

We see the request reach the endpoint, but we don't see the response handler kick in. Are there any optional configs that could interfere with the logout flow? We confirmed that the redirect URIs are what Zitadel is configured for.

@ahmednfwela
Copy link
Member

you can output debug logs from the package by enabling package:logging

also check if you have assigned postLogoutRedirectUri

Since I can't reproduce the issue on my end

@jlambright
Copy link
Author

jlambright commented Apr 2, 2024 via email

@jlambright
Copy link
Author

jlambright commented Apr 2, 2024

We eventually received a delayed OidcPreLogoutEvent, but it was severely delayed. However, we never receive the logout response. As a workaround, I'm forcing the app to forget the user, as we have verified that the auth server does receive the end session api call. I couple this with using some GetX observable flags for the core auth operations (e.g. login, logout, refresh) and execute logic when those flag values change.

  void monitorAuthServiceStreams() {
    final AuthService authService = Get.find<AuthService>();

    _authLastErrorStream = authService.latestError.stream;
    _authLastErrorStream.listen((exception) {
      if (exception == null) {
        _closeModal();
        return;
      }
      _showAuthErrorDialog(exception);
      return;
    });

    _userStream = authService.currentUser.stream;
    // _userStream.listen((userEvent) {
    //   final CommandService commandService = Get.find<CommandService>();
    //   if (commandService.isBusy.isTrue) {
    //     commandService.busyToggle();
    //   }
    //   if (userEvent == null) {
    //     if (Get.currentRoute != Routes.landing) {
    //       Get.offAllNamed(Routes.landing);
    //     }
    //   } else {
    //     if (Get.currentRoute == Routes.landing) {
    //       Get.offAllNamed(Routes.dispatch);
    //     }
    //   }
    // });

    _oidcEventStream = authService.oidcEventStream();
    _oidcEventStream.listen((event) {
      switch (event) {
        case OidcPreLogoutEvent(:final currentUser):
          Get.log('Pre-Logout Event: ${event.toString()} @ ${DateTime.now()}');
          break;
        default:
          Get.log(
              'Generic OIDC Event: ${event.toString()} @ ${DateTime.now()}');
          break;
      }
      return;
    });

    _isLoggingOutStream = authService.isLoggingOut.stream;
    _isLoggingOutStream.listen((isLoggingOut) {
      if (!isLoggingOut) {
        // Check if a dialog is open before attempting to close it
        _closeModal();
        return;
      }
      Get.offAllNamed(Routes.landing);
      _showLoggingOutModal();
      return;
    });

    _isLoggingInStream = authService.isLoggingIn.stream;
    _isLoggingInStream.listen((isLoggingIn) {
      if (!isLoggingIn) {
        if (authService.isAuthenticated) {
          if (Get.currentRoute == Routes.landing) {
            Get.offAllNamed(Routes.dispatch);
            _closeModal();
            return;
          }
        }
        Get.offAllNamed(Routes.landing);
        _closeModal();
        return;
      }
      _showLoggingInModal();
      return;
    });

    _isAuthRefreshingStream = authService.isRefreshing.stream;
    _isAuthRefreshingStream.listen((isRefreshing) {
      if (!isRefreshing) {
        // Check if a dialog is open before attempting to close it
        _closeModal();
        return;
      }
      _showAuthRefreshModal();
      return;
    });
  }

@jlambright
Copy link
Author

Is there any way you can add a timeout for this call? It feels like it shouldn't just sit there waiting for a response.

@ahmednfwela
Copy link
Member

@jlambright I will look into it

@ahmednfwela
Copy link
Member

ahmednfwela commented Jun 8, 2024

hi @jlambright sorry for the delay, if it's ok with you to send me the exact idp configuration you are using via email so I can debug this further, [email protected]

so far I have tested against zitadel and duende, and as long as you provide the correct postLogoutRedirectUri to OidcUserManager.settings the logout flow will work as intended

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working waiting for feedback
Projects
None yet
Development

No branches or pull requests

2 participants