auth0 / auth0-oidc-client-net

OIDC Client for .NET Desktop and Mobile applications
https://auth0.github.io/auth0-oidc-client-net/
Apache License 2.0
84 stars 48 forks source link

LogoutAsync Headless Browser Option #307

Closed justinh-phid closed 8 months ago

justinh-phid commented 9 months ago

Checklist

Describe the problem you'd like to have solved

Currently, when we the client calls LogoutAsync, an empty browser window pops up and closes. On WPF/.NET 6, this makes for a non-ideal user experience.

Describe the ideal solution

Here was the post I made in the community forums but I'm not sure that it is the solution I was looking for: https://community.auth0.com/t/logout-browser-visibility/121159

It would be ideal to provide a headless option (since the client is currently browser based) to allow applications to handle the UX of the logout independently. There appears to be an option parameter on the IdentityModel.OidcClient.LogoutRequest called BrowserDisplayMode which I think might allow for hiding the browser window.

Alternatives and current workarounds

Edit: If there are workarounds for this, I'd be open but assuming we can enable an option for logout, that may be a better use of time for all. I'm happy to open the PR if I can get some design guidance.

Additional context

Just wondering if this is a planned feature for the team or if other folks generally felt the same. I was thinking of exposing an option on LogoutAsync method but felt that exposing browser specific APIs on the client might muddy the waters for future implementations.

frederikprijck commented 9 months ago

Thanks for reaching out. Afaik the IdentityModel.OidcClient.LogoutRequest.BrowserDisplayMode isn't going to do anything by itself. Instead, this needs to be researched if and how every platform specific browser implementation (WPF, Winforms, UWP, MAUI, Xamarin on Android and Xamarin on iOS) supports this, and implemented it in all our browsers that support this throughout every platform's SDK.

That's quite a bit of work, and sadly not something I can prioritize at the moment.

Additionally, the opening of a browser on logout is something that we have on every platform because we need to logout from Auth0 as well.

If you are interested in opening working on a PR, I believe what needs to happen is the following:

If this wouldnt work on certain platforms, we can always consider moving forward without that. But I think we want to see if this is a feature I quickly gave this a try on WinForms and it seems to work fine.

Having said all of this, and given the scope of the work and the fact that this is the first time someone asked for this. I think it would make sense if you should solve this in your own code base:

In your case, it would most likely come down to changing

window.Show();

to

if (!isLogoutRequest) {
  window.Show();
}

Where you need to calculate isLogoutRequest based on the EndUrl. There is also a .Visible property on Window and WebView2, but I don't think you need those (but I can be wrong).

The workaround above allows you to test it, see how it behaves in production and consider contributing it back if you believe it actually improves DX alot without diving deep into our code base without knowing it does what you want.

justinh-phid commented 9 months ago

@frederikprijck Thanks for the detailed response and suggestions - really appreciate it. Heard/aligned on the scope of work and the general need for a feature at the moment.

I had a little time to play around with your second suggestion just to get a better understanding of the components needed to get this to work and effectively forked the WebViewBrowser implementation that's provided by your package (to directly edit code for now) and just did a crude string contains check for /logout in the StartUrl (my EndUrl was empty) to determine when when a logout request was being made:

if (!options.StartUrl.Contains("/logout"))
{
  window.Show();    
}

With the above modification the browser properly does not display. However, it appears that the logout request isn't being made either. (Tested by logging in, then logging out, then logging in again: no universal login page presented and previous user is authenticated)

I can probably spend a bit more time working on this tomorrow (this is for work) but may need to close this for now and revisit another time. I'll report back with any findings then. Thanks again for your guidance.

justinh-phid commented 8 months ago

Calling Window.Show() seems to be a required task to draw a new window instance with content. So I ended up just styling the window and moving it off the screen:

// Attempt to shrink window just for logout
if (options.StartUrl.Contains("/logout"))
{
      window.Top = -500;
      window.Left = -500;
      window.Height = 0;
      window.Width = 0;
      window.Visibility = Visibility.Hidden;
}

window.Show();

This workaround is easy enough to implement if someone else runs into this issue - I don't think we need to expose an option for it.

@frederikprijck Thanks for the help!

frederikprijck commented 8 months ago

I think i tried this and it worked by just not calling show(). But i was marking the webview2 as hidden etc as well, so maybe that behaves differently?

Anyway, happy you found a solution.

justinh-phid commented 8 months ago

@frederikprijck It's possible. I ran a quick test to see if modifying some of the visibility parameters helped any and came across something interesting. At logout time, I call the following:

# IBrowser Impl
// Attempt to shrink window just for logout
if (!options.StartUrl.Contains("/logout"))
{
     window.Show();
}
else
{
     window.Visibility = Visibility.Hidden;
}

# Logout Method
Log.Information("starting logout");
await _authClient.LogoutAsync();
Log.Information("logout complete");

The logs, however, result in the following:

{"@t":"2023-12-06T21:36:25.9651563Z","@m":"starting logout","@i":"3fb7456d","MachineName":"BLD"}
{"@t":"2023-12-06T21:36:37.3587263Z","@m":"Device status changed to: Ready","@i":"e0e17e7c","MachineName":"BLD"}

Effectively, the await doesn't return. Since we call logout when the user lands back at some start window (during window load), the process probably waits indefinitely but is not known to the user as it doesn't block the UI thread.

So at least in my setup, not calling window.Show() appears to not allow InvokeAsync() to return properly. I also tried to explicitly call window.Hide() next to the visibility call and that had no effect. Calling show and hide immediately after just causes the window to flash.

Yes, definitely happy to have a work around for now :)