User authentication

When it comes to user authentication, Rebex File Server is versatile. You can either choose to use the simple built-in virtual user database, or easily implement your own authentication provider.

Built-in user database 

By default, FileServer uses a built-in user database provided by its Users collection to authenticate connected users. It's very simple and non-persistent - just add one or more virtual users on startup and once you launch the server, it will work instantly.

Tip: UNC paths are supported on Windows systems. (Can be used to access network resources.)
Tip: You can also specifify which shell will be assigned to user.
// add some users
server.Users.Add("bob", "hispassword", @"c:\data\bob");
server.Users.Add("alice", "herpassword", @"c:\data\alice");

// this is actually equivalent
var robot = new FileServerUser("robot", "itspassword", @"c:\data\robot");
server.Users.Add(robot);

// removing users is simple as well
server.Users.Remove("bob");
server.Users.Remove(robot);

// and you can list the users easily
foreach (var user in server.Users)
{
    Console.WriteLine(user.Name);
}
' add some users
server.Users.Add("bob", "hispassword", "c:\data\bob")
server.Users.Add("alice", "herpassword", "c:\data\alice")

' this is actually equivalent
Dim robot = New FileServerUser("robot", "itspassword", "c:\data\robot")
server.Users.Add(robot)

' removing users is simple as well
server.Users.Remove("bob")
server.Users.Remove(robot)

' and you can list the users easily
For Each serverUser In server.Users
    Console.WriteLine(serverUser.Name)
Next

Tip: Instead of using the local file system, Rebex File Server makes it possible to create composite file systems by mounting multiple local directories into desired virtual paths. It supports custom file system providers as well.

Custom authentication provider 

When the built-in user database is not sufficient, you can easily implement a custom authentication provider using the Authentication event.

// register authentication event handler
server.Authentication += (sender, e) =>
{
    MyDbUser myUser;

    // try authenticating the user against a custom user database
    if (MyUserDatabase.TryAuthenticate(
        e.UserName, e.Password, out myUser))
    {
        // construct a user object
        var user = new FileServerUser(myUser.UserName, null, myUser.VirtualRoot);

        // accept authentication attempt with this user object
        e.Accept(user);
    }
    else
    {
        // reject authentication attempt
        e.Reject();
    }
};
' register authentication event handler
AddHandler server.Authentication,
    Sub(sender, e)
        Dim myUser As MyDbUser = Nothing

        ' try authenticating the user against a custom user database
        If MyUserDatabase.TryAuthenticate(e.UserName, e.Password, myUser) Then
            ' construct a user object
            Dim user = New FileServerUser(myUser.UserName, Nothing, myUser.VirtualRoot)

            ' accept authentication attempt with this user object
            e.Accept(user)
        Else
            ' reject authentication attempt
            e.Reject()
        End If
    End Sub
Note: When the Authentication event is registered, authentication against the built-in user database is not performed by default. It's completely up to you whether you accept or deny an authentication attempt.

The AuthenticationEventArgs object passed to the event handler contains information such as UserName, supplied Password or Key, ClientAddress and ClientEndPoint. It also provides a reference to the Users object, making it easily possible to utilize the built-in user database as well.

Username/password authentication 

By default, password-based authentication is used for authentication against the built-in user database and for custom authentication providers.

However, it can be disabled using Settings.AllowedAuthenticationMethods - for example if public key authentication is the only authentication method allowed.

Public key authentication 

Asymmetric cryptography makes it possible for a client to authenticate using a private key without revealing it to the SSH/SFTP/SCP server (or anyone else) - the server only needs to learn about the corresponding public key.

The need to store the public keys at the server complicates things a bit, but public key authentication can still be surprisingly simple. For example, if you only need to authenticate a single user:

// robot's user name
string userName = "robot";

// load robot's public key from a file
var publicKey = new SshPublicKey("robot.pub");

// robot's virtual root path
string virtualRoot = @"c:\data\robot";

// only allow 'public key' authentication
server.Settings.AllowedAuthenticationMethods = AuthenticationMethods.PublicKey;

// register authentication event handler
server.Authentication += (sender, e) =>
{
    // make sure that username and public key match the expected values
    if (e.UserName == userName && e.Key != null && e.Key.Equals(publicKey))
    {
        e.Accept(new FileServerUser(e.UserName, null, virtualRoot));
    }
    else
    {
        e.Reject();
    }
};
' robot's user name
Dim userName As String = "robot"

' load robot's public key from a file
Dim publicKey As New SshPublicKey("robot.pub")

' robot's virtual root path
Dim virtualRoot As String = "c:\data\robot"

' only allow 'public key' authentication
server.Settings.AllowedAuthenticationMethods = AuthenticationMethods.PublicKey

' register authentication event handler
AddHandler server.Authentication,
    Sub(sender, e)
        ' make sure that username and public key match the expected values
        If e.UserName = userName AndAlso e.Key IsNot Nothing AndAlso e.Key.Equals(publicKey) Then
            e.Accept(New FileServerUser(e.UserName, Nothing, virtualRoot))
        Else
            e.Reject()
        End If
    End Sub

Note: When the Authentication event has been called, it means that the server already made sure that the client is in possession of the corresponding private key.

Note: For the sake of simplicity, we have been comparing public key fingerprints (hashes) in some of our samples. Please be aware that this is not considered sufficiently secure. Make sure to compare whole public keys in production code.

Check out the Advanced authentication provider section for a slightly more advanced sample code.

Password and public key authentication 

Authentication using both password and public key is slightly more complicated due to the nature of the SSH authentication protocol. The credentials are provided by the client sequentially, which means that the Authentication event is going to be called twice. During the first call, only accept the credential partially.

// robot's user name
string userName = "robot";

// robot's password
string password = "password";

// load robot's public key from a file
var publicKey = new SshPublicKey("robot.pub");

// robot's virtual root path
string virtualRoot = @"c:\data\robot";

// allow 'public key' and password authentication
server.Settings.AllowedAuthenticationMethods = AuthenticationMethods.PublicKey | AuthenticationMethods.Password;

// register authentication event handler
server.Authentication += (sender, e) =>
{
    if (e.UserName == userName)
    {
        bool correct = false;

        if (e.Key != null)
        {
            // when authenticating using a key, make sure it is correct
            if (e.Key.Equals(publicKey))
            {
                correct = true;
            }
        }
        else if (e.Password != null)
        {
            // when authenticating using a password, make sure it is correct
            if (e.Password == password)
            {
                correct = true;
            }
        }

        if (correct)
        {
            if (e.PartiallyAccepted)
            {
                // if another kind of credential has already been provided
                // and accepted, authenticate the user
                e.Accept(new FileServerUser(e.UserName, null, virtualRoot));
                return;
            }
            else
            {
                // if another kind of credential has not been provided yet,
                // only accept the current one partially
                e.AcceptPartially();
                return;
            }
        }
    }

    // if no correct credential was supplied, reject this authentication attempt
    e.Reject();
};
' robot's user name
Dim userName As String = "robot"

' robot's password
Dim password As String = "password"

' load robot's public key from a file
Dim publicKey As New SshPublicKey("robot.pub")

' robot's virtual root path
Dim virtualRoot As String = "c:\data\robot"

' allow 'public key' and password authentication
server.Settings.AllowedAuthenticationMethods = AuthenticationMethods.PublicKey Or AuthenticationMethods.Password

' register authentication event handler
AddHandler server.Authentication,
    Sub(sender, e)
        ' make sure that username and public key match the expected values
        If e.UserName = userName Then
            Dim correct As Boolean = False

            If e.Key IsNot Nothing Then
                ' when authenticating using a key, make sure it is correct
                If e.Key.Equals(publicKey) Then correct = True
            ElseIf e.Password IsNot Nothing Then
                ' when authenticating using a password, make sure it is correct
                If e.Password = password Then correct = True
            End If

            If correct Then
                If e.PartiallyAccepted Then
                    ' if another kind of credential has already been provided
                    ' and accepted, authenticate the user
                    e.Accept(New FileServerUser(e.UserName, Nothing, virtualRoot))
                    Return
                Else
                    ' if another kind of credential has not been provided yet,
                    ' only accept the current one partially
                    e.AcceptPartially()
                    Return
                End If
            End If
        End If

        ' if no correct credential was supplied, reject this authentication attempt
        e.Reject()
    End Sub
Note: Please be aware that some clients supply the password first, while others supply the public key first.

Advanced authentication provider 

In many cases, it's easier to reuse the built-in user database instead of writing your own. For example, if all you need is to add public key authentication support, you might consider the following approach.

First, extend the built-in FileServerUser object:

public class UserWithKey : FileServerUser
{
    public SshPublicKey Key { get; private set; }

    public UserWithKey(string userName, SshPublicKey key, string physicalRootPath)
        : base(userName, null, physicalRootPath)
    {
        Key = key;
    }
}
Public Class UserWithKey
    Inherits FileServerUser
    Public Property Key() As SshPublicKey
        Get
            Return m_Key
        End Get
        Private Set(value As SshPublicKey)
            m_Key = value
        End Set
    End Property
    Private m_Key As SshPublicKey

    Public Sub New(userName As String, sshKey As SshPublicKey, physicalRootPath As String)
        MyBase.New(userName, Nothing, physicalRootPath)
        Key = sshKey
    End Sub
End Class

Then, add some users and implement an authentication provider that supports both password-based and key-based authentication methods:

// allow 'password' and 'public key' authentication
server.Settings.AllowedAuthenticationMethods =
    AuthenticationMethods.PublicKey | AuthenticationMethods.Password;

// add users that use passwords
server.Users.Add("bob", "hispassword", @"c:\data\bob");
server.Users.Add("alice", "herpassword", @"c:\data\alice");

// add users that use public keys
var robotKey = new SshPublicKey("robot.pub");
server.Users.Add(new UserWithKey("robot", robotKey, @"c:\data\robot"));

var serviceKey = new SshPublicKey("service.pub");
server.Users.Add(new UserWithKey("service", serviceKey, @"c:\data\service"));

// register authentication event handler
server.Authentication += (sender, e) =>
{
    // make sure the user exists
    FileServerUser user = server.Users[e.UserName];
    if (user != null)
    {
        // if it's a 'public key' user, check the key
        var userWithKey = user as UserWithKey;
        if (userWithKey != null && userWithKey.Key.Equals(e.Key))
        {
            e.Accept(userWithKey);
            return;
        }

        // otherwise, check the password
        if (user.CheckPassword(e.Password))
        {
            e.Accept(user);
            return;
        }
    }

    e.Reject();
};
' allow 'password' and 'public key' authentication
server.Settings.AllowedAuthenticationMethods = AuthenticationMethods.PublicKey Or AuthenticationMethods.Password

' add users that use passwords
server.Users.Add("bob", "hispassword", "c:\data\bob")
server.Users.Add("alice", "herpassword", "c:\data\alice")

' add users that use public keys
Dim robotKey = New SshPublicKey("robot.pub")
server.Users.Add(New UserWithKey("robot", robotKey, "c:\data\robot"))

Dim serviceKey = New SshPublicKey("service.pub")
server.Users.Add(New UserWithKey("service", serviceKey, "c:\data\service"))

' register authentication event handler
AddHandler server.Authentication,
    Sub(sender, e)
        ' make sure the user exists
        Dim user As FileServerUser = server.Users(e.UserName)
        If user IsNot Nothing Then
            ' if it's a 'public key' user, check the key
            Dim userWithKey = TryCast(user, UserWithKey)
            If userWithKey IsNot Nothing AndAlso userWithKey.Key.Equals(e.Key) Then
                e.Accept(userWithKey)
                Exit Sub
            End If

            ' otherwise, check the password
            If user.CheckPassword(e.Password) Then
                e.Accept(user)
                Exit Sub
            End If
        End If

        e.Reject()
    End Sub

In addition to this, you might want to register an additional PreAuthentication event. It's called when authentication is just about to start and makes it possible to specify the authentication methods that are allowed for the user attempting authentication. In this case, we only allow 'public key' authentication when appropriate, preventing other users from even attempting key-based authentication:

// register pre-authentication event handler
server.PreAuthentication += (sender, e) =>
{
    // determine which authentication methods to allow based on the user name
    FileServerUser user = server.Users[e.UserName];
    if (user is UserWithKey)
    {
        // only allow public key authentication for key-only users
        e.Accept(AuthenticationMethods.PublicKey);
    }
    else
    {
        // only allow password authentication for the rest
        // (even if the user doesn't exist, pretend it does)
        e.Accept(AuthenticationMethods.Password);
    }
};
' register pre-authentication event handler
AddHandler server.PreAuthentication,
    Sub(sender, e)
        ' determine which authentication methods to allow based on the user name
        Dim user As FileServerUser = server.Users(e.UserName)
        If TypeOf user Is UserWithKey Then
            ' only allow public key authentication for key-only users
            e.Accept(AuthenticationMethods.PublicKey)
        Else
            ' only allow password authentication for the rest
            ' (even if the user doesn't exist, pretend it does)
            e.Accept(AuthenticationMethods.Password)
        End If
    End Sub

Certificate authentication 

Certificate-based client authentication is also supported. To achieve this, use the mechanism described above for public key authentication. To perform client certificate validation as well, obtain the certificate from the SshPublicKey object by calling its GetCertificate or GetCertificateChain method and validate it by calling its Validate method.