More .NET components

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.

CSharp

// 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);
}

VisualBasic

' 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.

CSharp

// 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();
    }
};

VisualBasic

' 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 (or its fingerprint).

The need to store the public keys (or at least their fingerprints) 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:

CSharp

// user name
string userName = "robot";

// fingerprint of robot's public key
string fingerprint = "f1:d0:c9:2d:4c:2e:9c:eb:bf:a8:da:bb:8d:e3:cf:f8";

// 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) =>
{
    if (e.UserName == userName && e.Key.Fingerprint.ToString() == fingerprint)
        e.Accept(new FileServerUser(e.UserName, null, virtualRoot));
    else
        e.Reject();
};

VisualBasic

' user name
Dim userName As String = "robot"

' fingerprint of robot's public key
Dim fingerprint As String = "f1:d0:c9:2d:4c:2e:9c:eb:bf:a8:da:bb:8d:e3:cf:f8"

' 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)
        If e.UserName = userName AndAlso e.Key.Fingerprint.ToString() = fingerprint Then
            e.Accept(New FileServerUser(e.UserName, Nothing, virtualRoot))
        Else
            e.Reject()
        End If
    End Sub

Check out the next section for a slightly more advanced sample code.

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:

CSharp

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

    public UserWithKey(string userName, SshPublicKey key, string physicalRootPath)
        : base(userName, null, physicalRootPath)
    {
        Key = key;
    }
}

VisualBasic

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:

CSharp

// 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();
};

VisualBasic

' 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:

CSharp

// 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);
    }
};

VisualBasic

' 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.