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