+2 votes
by (230 points)

Hello,

I'm exploring the guidance in Custom authentication provider to implement some custom auth logic using the PreAuthentication and Authentication event handlers, but my logic requires calling into external services that require calling async code.

The trouble is because it's async called by sync event handler, the Server thinks my response is complete before my logic is even run. Any guidance on how to address this? I assume you don't have some alternative async approach to the Authentication even handler?

1 Answer

0 votes
by (5.3k points)
selected by
 
Best answer

Hi jwick,
thanks for the question.
All SFTP server events are synchronous now. We probably redesign events when we will have async SSH core.

When another library provides only async API, you have to use the "sync over async" call as the smallest evil. We are aware that "sync over async" call is far from ideal and may cause problems when your server is under heavy load (using more threads than necessary, thread pool starvation).

Please try the following code.

          //Handle Authentication event to authenticate users against the database. Don't use await in the **async void** event handler, delegate to Task-based method and Wait for the result. 
    _sftpServer.Authentication += (sender, args) => SftpServerAuthentication(sender, args).Wait();

//Always use ConfigureAwait(false) in the 'await' expression.

    /// <summary>
    /// Handler for the <see cref="Server.Authentication"/> event.
    /// </summary>
    private static async Task SftpServerAuthentication(object sender, AuthenticationEventArgs e)
    {
      //Create 'anonymous' FileSystemDbContext for administrative task - user authentication.
      using var context = new FileSystemDbContext(_options);

      //Read user name from AuthenticationEventArgs.
      var currentUserName = e.UserName;

      //Load a user from database.  
      var user = await context.GetUserByName(currentUserName).ConfigureAwait(false);

      //When a user is not in our database, deny access.
      if (user == null)
      {
        e.Reject();
        return;
      }

      //Verify user password
      if (!_passwordHashService.VerifyPassword(user, e.Password))
      {
        //When user sends invalid password, deny access.
        e.Reject();
        return;
      }

      //Create "user session scoped" instance of the FileSystemDbContext.
      var fileSystemDbContext = new FileSystemDbContext(_options, currentUserName);
      //Create "user session scoped" instance of the EfFileSystemProvider.
      var efFileSystemProvider = new EfFileSystemProvider(fileSystemDbContext);
      //Create instance of our DbFileServerUser with user specific file system.
      var dbFileServerUser = new DbFileServerUser(currentUserName, efFileSystemProvider);

      //Authentication succeeded. Allow user logon.
      e.Accept(dbFileServerUser);
    }
by (230 points)
Thanks for the response, I was "afraid" you would say that ;-)

But at least I have confirmation and certainty there isn't currently a better approach.

For now I'm playing around with using the .Wait(timeout) variation to limit the possibility of a lock up, since most authentication attempts should be completed within a minute or two.
by (5.3k points)
Thanks for letting me know.

Yes, Wait method with timeout is certainly better.
Unfortunately, there is no simple solution for bridging the gap between the sync and the async world, and only tweaks like support for the timeout or cancellation are possible.
It is also possible to 'hijack' the already waiting thread in the event handler and marshal all continuations from thread pool threads to this thread using the custom synchronization context. I am afraid that the solution would be very complex and still does not solve the main issue.
by (150 points)
Hi,

I am running into the same issue.  Do you have a timeframe for implementing an async version of the sftp fileserver?
by (147k points)
Hi, the async version of Rebex File Server has been postponed to version 8.0 (scheduled for next year) due to enduring popularity of legacy platforms such as .NET Compact Framework or .NET Framework 2.0 (which are still going to be supported in version 7.0 due to be published next month).

However, the synchronous nature of authentication events is apparently the most problematic aspect of the current approach, so we decided to look again into the possibility of addressing this issue as the first step of making the whole internals fully asynchronous. And fortunately, it looks like it would actually be possible with relatively few changes in the code and the API.

So we'll give it a try now and hopefully have an improved build of Rebex File Server next week. We could then keep it in sync with the 7.0 version and make it part of the forthcoming 8.0 release. If you are interested in this approach, please let me know and I'll send you a link to the improved version as soon as it's ready!
by (150 points)
Sounds great.  Would love to test it out when it is ready.
by (147k points)
Hello, I sent a link to an enhanced build to your email address. Please give it a try and let us know whether it works as expected.
by (190 points)
Hello, we created a SFTP server with Rebex.FileServer 5.0.7900 and we encountered a thread starvation issue, so we had to restart the service. The core dump show there was a lot of dead threads. Can you send me a link for the async version of Rebex.FileServer, I want to confirm whether it's related to asynchronous operations.
by (147k points)
I will send you the link when a new build is ready!
by (147k points)
Sorry for the delay, here is the latest build:
https://www.rebex.net/getfile/ae65f807936c4b0cb04dd86ad37ce6ac/RebexFileServer-8.0.8879-Async-Preview1.zip
This is a trial build, but can be activated using your key from https://www.rebex.net/protected/nuget/
The low-level SSH library is fully asynchronous now, but higher levels are not fully converted yet.
...