1

For a Winforms Desktop application I will use the authorization code flow with PKCE. As Identity provider I use IdentityServer and as client library OicdClient. Next step I have to decide which Browser to use for the user login:

For SystemBrowser speaks the simple/clear implementation of the flow. For Extended WebBrowser speaks that some user may have no SystemBrowser. But the WebBrowser is an older IE version? and is it allowed to use for a secure authentication?

Nevertheless I tried the "Extended WebBrowser" Sample and stumble integrating it in to my prototype Environment with own IS4 server. Therefore I need some clarity with the code flow and redirect. I already had implemented this authorization code flow with pure .Net classes, but using OicdClient makes me little confusing(in the beginning like a black box).

My question is how does the redirect work with this libraries and who are responsible for redirect and who are responsible to receive the redirect with the code (to exchange for access token)?

The code flow has following steps (without details like clientID, PKCE ...):

  1. Send a code request to IS4
  2. IS4 Response with a login page (shown in a Browser)
  3. After successful login IS4 sends to redirect URL with code
  4. A HttpListener receives this redirect with code and
  5. Send a request to IS4 with the code to receive a access token

With OidcClient and using the Automatic Mode:

var options = new OidcClientOptions
{
    Authority = "https://demo.identityserver.io",
    ClientId = "native",
    RedirectUri = redirectUri,
    Scope = "openid profile api",
    Browser = new SystemBrowser()
};

var client = new OidcClient(options);
var result = await client.LoginAsync();

Here is to much magic for me. Only a call to LoginAsync() makes it work...

An important point seems to be the Browser property of the options with the IBrowser interface and its implementation of this method:

    public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken)
    {
            using (var listener = new LoopbackHttpListener(Port, _path))
            {
                OpenBrowser(options.StartUrl);
                try
                {
                    var result = await listener.WaitForCallbackAsync();
                    if (String.IsNullOrWhiteSpace(result))
                    {
                        return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = "Empty response." };
                    }
                    return new BrowserResult { Response = result, ResultType = BrowserResultType.Success };
                }
                catch (TaskCanceledException ex)
                { ....}
            }
        }

if I try to map to the flow steps:

  1. Login page: OpenBrowser(options.StartUrl);
  2. Redirect will be done by IS4? The SystemBrowser from sample does not do this.
  3. Receive the code: await listener.WaitForCallbackAsync();

1 and 5 are probably done by the OicdClient. This Example is fairly clear, need confimation that redirect is done by IS4.

The implementation in the other example Extended WebBrowser

public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var form = _formFactory.Invoke())
            using (var browser = new ExtendedWebBrowser()
            {
                Dock = DockStyle.Fill
            })
            {
                var signal = new SemaphoreSlim(0, 1);

                var result = new BrowserResult
                {
                    ResultType = BrowserResultType.UserCancel
                };

                form.FormClosed += (o, e) =>
                {
                    signal.Release();
                };

                browser.NavigateError += (o, e) =>
                {
                    e.Cancel = true;

                    if (e.Url.StartsWith(options.EndUrl))
                    {
                        result.ResultType = BrowserResultType.Success;
                        result.Response = e.Url;
                    }
                    else
                    {
                        result.ResultType = BrowserResultType.HttpError;
                        result.Error = e.StatusCode.ToString();
                    }

                    signal.Release();
                };

                browser.BeforeNavigate2 += (o, e) =>
                {
                    var b = e.Url.StartsWith(options.EndUrl);
                    if (b)
                    {
                        e.Cancel = true;
                        result.ResultType = BrowserResultType.Success;
                        
                        result.Response = e.Url;
                        
                        signal.Release();
                    }
                };

                form.Controls.Add(browser);
                browser.Show();

                System.Threading.Timer timer = null;

                form.Show();
                browser.Navigate(options.StartUrl);

                await signal.WaitAsync();
                if (timer != null) timer.Change(Timeout.Infinite, Timeout.Infinite);

                form.Hide();
                browser.Hide();

                return result;
            }
        }
  1. Done by: browser.Navigate(options.StartUrl);
  2. Redirect by IS4
  3. Receive of code in event handle: NavigateError ???

Is here something wrong? On IS4 the AccountController.Login is called that calls /connect/authorize/callback? with the redirect_uri. But this doesn't come to BeforeNavigate2. instead NavigateError event appears where the result set to:

result.ResultType = BrowserResultType.Success;
result.Response = e.Url; 
Mustafa
  • 77
  • 8

1 Answers1

1

Current best practice is to use the user's default web browser and not to embed a browser component. As for how to implement that - since you can't intercept browser navigation events using this approach you'd need to implement an HTTP listener that can accept the POST request from your identityserver4 implementation.

Have a read of this: https://auth0.com/blog/oauth-2-best-practices-for-native-apps/

And this RFC: https://tools.ietf.org/html/rfc8252

mackie
  • 4,061
  • 1
  • 15
  • 17