Simple .NET Authentication Extensions

In many (or even most) cases, authenticators can be written as described below. If this is not flexible enough for a particular application, an advanced .NET Authentication Extension may be required.

The only method that needs to be implemented is LoadedUserInfo LoadUserInfo(IUserInfo suppliedUserInfo). When the server calls this method in your authenticator, it supplies user information that enables your extension to look up that user's authentication details from an external source. You then return a LoadedUserInfo object from this method, which contains the user's home directory, password hash and/or public keys. The authenticator caches this loaded user object, and uses it to authenticate the user.

See the class reference for more details of the classes and interfaces involved.

Example 1 shows sample source code for implementing password authentication. See Example 2 for an example of public key authentication and password authentication.

Optionally, the void SetPassword(IUserInfo suppliedUserInfo, string oldPassword, string newPassword) can also be implemented. This can be used to change the user's password in the external user data source. Ultimately, this method is called via the FTP SITE command, CPWD, or via the SSH/SFTP password changing mechanism.

SSH/SFTP also permits servers to force a password change on clients. This can be implemented by setting the LoadedUserInfo.MustChangePassword property on the LoadedUserInfo object that is returned from LoadUserInfo. FTP does not permit the server to force a user to change their password.

See Example 3 for how to force and implement password changes in SSH.

Example 1

Below is the source code for a sample authenticator which supports password authentication. For simplicity, the password is hardcoded, whereas in production code, it would be retrieved from a data source. The password is not stored - instead, the PasswordHash field is populated when the Password field is set. The PasswordHash field can be set directly - normally only password hashes would be stored.

using EnterpriseDT.Net.FtpServer.Core;

namespace EnterpriseDT.Net.FtpServer.Extensions.Test
{
    public class SimplePasswordAuth : Authenticator
    {
        public override LoadedUserInfo LoadUserInfo(IUserInfo suppliedUserInfo)
        {
            if (suppliedUserInfo.UserName=="fred")
            {
                LoadedUserInfo info = new LoadedUserInfo();
                info.Password = "fredspassword";
                return info;
            }
            else
                return null;
        }
    }
}

Example 2

Below is the source-code for a sample authenticator which allows a user to log via SFTP using public key authentication or by password authentication. For simplicity, the public keys are hard coded, whereas in production code they would be retrieved from a data source.

using System.Text;
using EnterpriseDT.Net.FtpServer.Core;

namespace EnterpriseDT.Net.FtpServer.Extensions.Test
{
	public class PublicKeyTest : Authenticator
	{
		public override LoadedUserInfo LoadUserInfo(IUserInfo suppliedUserInfo)
		{
			LoadedUserInfo info = new LoadedUserInfo();
			info.RSAPublicKeys.Add(RSAPublicKey);
			info.DSAPublicKeys.Add(DSAPublicKey);
			info.Password = "mypassword";
			return info;
		}

		private byte[] RSAPublicKey
		{
			get
			{
				string key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" +
					"Comment: \"imported-openssh-key\"\r\n" +
					"AAAAB3NzaC1yc2EAAAABIwAAAIEA44J6LBloMWVvhOjMHZPnmgJWw+UWBl9nFEWa\r\n" +
					"62IFDrJDg6+kJ2DQD8vOTsQmNjk88O3v+r0r/rr+QrotuLrdjrXBvrRrQNMfEbMo\r\n" +
					"LhSmUVEFR/Yy3HjVRT6DHhJYPpr1xaXE6++fo5b2ax1zw+d1fPsh53lbAhrCHV9b\r\n" +
					"NIOimDk=\r\n" +
					"---- END SSH2 PUBLIC KEY ----";
				return Encoding.ASCII.GetBytes(key);
			}
		}

		private byte[] DSAPublicKey
		{
			get
			{
				string key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" +
					"Comment: \"imported-openssh-key\"\r\n" +
					"AAAAB3NzaC1kc3MAAACBAPos9tWoXLcd//dOGbaA+1TCO9vEi0jQOQM85j34E4Ua\r\n" +
					"Sza5yjS3vI9K9XchJirbNYrRQNmgM2yn3fUDdTPU5eES+mZRy9K9qpAesk4Ghpwu\r\n" +
					"btWc3e0APkQTUAoRHL8yiW1tHrRdV6yrowgKDPrIccnL90wYAZFHmUmwIeiESjTB\r\n" +
					"AAAAFQDhvm9w83LDeixC3oPW+FOKk673dQAAAIBObehA6t+eRtNTocY1sb7Dly0O\r\n" +
					"ReeRWo+mHEyUts78ayAN7YFNzTd8UXmUgw8gyGFtO/tXrkeLG46vMhL/0402ek9Z\r\n" +
					"jNcDq2vF1InYIaOxceuRqg99VGQUqrjEWchIG5egDgtOKRAUtUyK7I52CXG3wN9/\r\n" +
					"2Oq+WOoUztJCSwgmwwAAAIEAu4G5CHifmoTsBVcObaRkW8UqrTCmz7C84W6AaXA5\r\n" +
					"uBlwtTIBlAUnKzfqStpC76rucJ6i3R9Nk+gHrDb4v6uA6at2UZDlHZlwPCg88fk7\r\n" +
					"Nbi5umH9B/QSfm+GQOd+ttD54FOcR+lwmerJ+f1mzSX9v9ZrVi+xJJ6Jp+5NDa7g\r\n" +
					"KtM=\r\n" +
					"---- END SSH2 PUBLIC KEY ----";
				return Encoding.ASCII.GetBytes(key);
			}
		}

	}
}

Example 3

Below is the source-code for a sample authenticator which allows a user to login via SFTP by password authentication, but forces the user to change their password. This is an SSH-specific mechanism that many SSH clients support.

There are three requirements for doing this. Firstly, the LoadedUserInfo object must have the MustChangePassword property set to true. The void SetPassword(IUserInfo suppliedUserInfo, string oldPassword, string newPassword) method must be overridden to allow the new password to be set - throw an InvalidPasswordException if the new password does not pass the required checks. And finally, in the Password policies dialog found from the link in the General User Settings dialog box (accessible from the Users tab in the manager), Permit password changes must be selected.

using EnterpriseDT.Net.FtpServer.Core;

namespace EnterpriseDT.Net.FtpServer.Extensions.Test
{
	public class ChangePasswordAuth : Authenticator
	{
		private const string PASSWORD = "javaftp";
		private string password = PASSWORD;

		public override LoadedUserInfo LoadUserInfo(IUserInfo suppliedUserInfo)
		{
			LoadedUserInfo info = new LoadedUserInfo();
			info.Password = password;
			info.MustChangePassword = (password == PASSWORD);
			return info;
		}

		public override void SetPassword(IUserInfo suppliedUserInfo, string oldPassword, string newPassword)
		{
			if (oldPassword == newPassword)
				throw new InvalidPasswordException("New password must be different");
			password = newPassword;
		}
	}
}