0 votes
by (180 points)

Hi
we are using .net rebex.terminal, Ssh, Common version 5.0.7077.0. and during keyboard interactive login in AuthenticationRequest handler we use await to get user answer from keyboard. issue is when we use await, next question is comming in seconds after previous, without waiting of answer. without await keyword it is working as expected.
what is the best practice to handle keyboard interactive auth method in WPF applications?

thank you in advance!

1 Answer

0 votes
by (146k points)

Hi,

The await keyword can't actually be used in event handlers that need to modify anything in the event arguments after the first await occurrence, because at that point the event handler ends and returns to the caller. The rest of the code (after await) is going to run as well, but the event handler has no means to wait for it. This is due to the nature of .NET events. To make it possible to use await within an event such as AuthenticationRequest, the event would have to return a Task to the caller which the authentication code could use to wait for the async code to finish, but this is not currently supported in Rebex Terminal (we will most likely add this on a future release).

The most simple way to handle interactive authentication in WPF applications is to use a dialog to handle authentication prompts. This can be implemented without a need to use any await in the event handler:

private async void ConnectClickHandler(object sender, RoutedEventArgs e)
{
    ssh = new Ssh();

    await ssh.ConnectAsync("test.rebex.net", 22);

    ssh.AuthenticationRequest += SuthenticationRequestHandler;
    await ssh.LoginAsync();
    ssh.AuthenticationRequest -= SuthenticationRequestHandler;

    string response = await ssh.RunCommandAsync("uname -a");
    textBlock.Text = response;
}

private void SuthenticationRequestHandler(object sender, SshAuthenticationRequestEventArgs e)
{
    var dialog = new LogOnWindow();
    dialog.Arguments = e;

    if (!dialog.ShowDialog().GetValueOrDefault())
    {
        e.Cancel = true;
    }
}

Alternatively, if you really need to use an AuthenticateionRequest event handler with await in a WPF application, you can achieve that using a "proxy" handler. The code below demonstrates one possible way to implement this:

private async void ConnectClickHandler(object sender, RoutedEventArgs e)
{
    ssh = new Ssh();

    await ssh.ConnectAsync("test.rebex.net", 22);

    // raise events from the "current" thread (which actually means a background thread when async methods are used)
    ssh.Settings.RaiseEventsFromCurrentThread = true;
enter code here
    ssh.AuthenticationRequest += SuthenticationRequestHandlerProxy;
    await ssh.LoginAsync();
    ssh.AuthenticationRequest -= SuthenticationRequestHandlerProxy;

    // raise event "normally" again
    ssh.Settings.RaiseEventsFromCurrentThread = false;

    string response = await ssh.RunCommandAsync("uname -a");
    textBlock.Text = response;
}

private void SuthenticationRequestHandlerProxy(object sender, SshAuthenticationRequestEventArgs e)
{
    // create TaskCompletionSource as a simple way to retrieve the SuthenticationRequestHandler's Task when it becomes available
    var tcs = new TaskCompletionSource<Task>();

    // invoke SuthenticationRequestHandler using the current window's dispatcher (and use the TaskCompletionSource to pass when finished)
    Dispatcher.Invoke(() =>
    {
        SuthenticationRequestHandler(sender, e).ContinueWith(t => tcs.SetResult(t));
    });

    // get SuthenticationRequestHandler's Task (waits until SuthenticationRequestHandler finished and tcs.SetResult has been called)
    var task = tcs.Task.GetAwaiter().GetResult();

    // call GetResult() on the already-finished SuthenticationRequestHandler's Task (to re-raise an exceptions if any occured)
    task.GetAwaiter().GetResult();
}

private async Task SuthenticationRequestHandler(object sender, SshAuthenticationRequestEventArgs e)
{
    // here you can freely use 'await'
}

Here, the SuthenticationRequestHandlerProxy event handler actually waits for the "real" event handler to complete before returning to the caller. However, it runs in a separate background thread, so the WPF application thread is not blocked.

by (180 points)
thanks! it is working for us.
...