Rebex File Server
SFTP, SCP and SSH server library for .NET
Download 30-day free trial Buy from $349More .NET libraries
-
Rebex SFTP
.NET SFTP client
-
Rebex FTP
.NET FTP client
-
Rebex Total Pack
All Rebex libraries together
Back to feature list...
User authentication
On this page:
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.
// 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
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
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.
Back to feature list...