In yesterday’s post, “Leveraging LDAPS Authentication in C# & .NET”, we looked at how to leverage LDAPS authentication in our applications, and the fact that it is more secure than LDAP.

In today’s post, we will look at a third alternative - StartTLS.

This is also an improvement over LDAP, as it starts off as LDAP and then upgrades to an encrypted connection. This makes it more flexible and just as secure as LDAPS.

The code again is very similar to the LDAP and LDAPS code.

using System.DirectoryServices.Protocols;
using System.Net;

namespace Authenticators;

public sealed class StartTLSAuthenticator : IAuthenticator
{
    private const int StartTLSPort = 389;
    private readonly string _domain;

    public StartTLSAuthenticator(string domain)
    {
        ArgumentException.ThrowIfNullOrEmpty(domain);
        _domain = domain;
    }

    public void Authenticate(string username, string password)
    {
        ArgumentException.ThrowIfNullOrEmpty(username);
        ArgumentException.ThrowIfNullOrEmpty(password);
        try
        {
            var identifier = new LdapDirectoryIdentifier(_domain, StartTLSPort);

            var connection = new LdapConnection(identifier)
            {
                Credential = new NetworkCredential(username, password),
                AuthType = AuthType.Negotiate
            };

            // Require TLS upgrade
            connection.SessionOptions.ProtocolVersion = 3;

            // Upgrade to TLS (StartTLS)
            connection.SessionOptions.StartTransportLayerSecurity(null);

            // Now the connection is encrypted
            connection.Bind();
        }
        catch (LdapException ex)
        {
            throw new AuthenticationException($"Ldap Exception: {ex.Message}", ex);
        }
        catch (Exception ex)
        {
            throw new AuthenticationException($"General Authentication Exception: {ex.Message}", ex);
        }
    }
}

The magic is happening here:

// Require TLS upgrade
connection.SessionOptions.ProtocolVersion = 3;

// Upgrade to TLS (StartTLS)
connection.SessionOptions.StartTransportLayerSecurity(null);

Next, some tests.

using Authenticators;
using AwesomeAssertions;

namespace Tests;

[Trait("Category", "LDAPS")]
public class StartTLSAuthenticatorTests
{
    [Theory]
    [InlineData("yourDomain.com", "yourUser", "yourPassword")]
    public void Valid_Username_And_ValidPassword_Succeeds(string domain, string user, string password)
    {
        var authenticator = new StartTLSAuthenticator(domain);
        var act = () => authenticator.Authenticate(user, password);
        act.Should().NotThrow();
    }

    [Theory]
    [InlineData("yourDomain.com", "yourUser", "INVALID")]
    public void Valid_Username_And_InValidPassword_Fails(string domain, string user, string password)
    {
        var authenticator = new StartTLSAuthenticator(domain);
        var act = () => authenticator.Authenticate(user, password);
        act.Should().Throw<AuthenticationException>();
    }

    [Fact]
    public void Null_Domain_Throws_Exception()
    {
        var act = () => new StartTLSAuthenticator("");
        act.Should().Throw<ArgumentException>();
    }

    [Fact]
    public void Null_UserName_Throws_Exception()
    {
        var authenticator = new StartTLSAuthenticator("yourDomain.com");
        var act = () => authenticator.Authenticate("", "password");
        act.Should().Throw<ArgumentException>();
    }

    [Fact]
    public void Null_Password_Throws_Exception()
    {
        var authenticator = new StartTLSAuthenticator("yourDomain.com");
        var act = () => authenticator.Authenticate("user", "");
        act.Should().Throw<ArgumentException>();
    }
}

Thus, we can enjoy the security of LDAPS with the convenience of LDAP.

TLDR

You can use StartTLS for authentication in your applications using the types in the System.DirectoryServices.Protocols namespace.

The code is in my GitHub.

Happy hacking!