46

I have found a lot of information from the past saying that LDAP authentication isn't enabled yet but you can get around that using third party packages. However, it seems that LDAP authentication WAS implemented back in January. I can't seem to find any information on HOW to implement it.

I already have custom authentication set up in my project, I just need the logic to fill in the HandleAuthenticateAsync method.

I have tried using other examples, but they don't seem to work with .NET Core 2.0.

Here is the only relevant code that I have that I can think of posting

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
    // Get Authorization header value
    if (!Request.Headers.TryGetValue(HeaderNames.Authorization, out var authorization)) {
        return Task.FromResult(AuthenticateResult.Fail("Cannot read authorization header."));
    }

    // TODO: Authenticate user

    // Create authenticated user ticket
    var identities = new List<ClaimsIdentity> { new ClaimsIdentity("custom auth type") };
    var ticket = new AuthenticationTicket(new ClaimsPrincipal(identities), Options.Scheme);

    return Task.FromResult(AuthenticateResult.Success(ticket));

    // else User not authenticated
    return Task.FromResult(AuthenticateResult.Fail("Invalid auth key."));
}

So, my question is, how do I implement LDAP Authentication in .NET Core 2.0?

Window
  • 1,189
  • 1
  • 14
  • 22

4 Answers4

46

Thanks to Win's Answer for pointing out that I needed to use Windows Compatibility Pack, I was able to figure this out.

The first thing I had to do was install the Nuget package

Install-Package Microsoft.Windows.Compatibility 

At the time, I needed a preview version, so I appended -Version 2.0.0-preview1-26216-02 on the end of this command

Then, add using statements for System.DirectoryServices and System.DirectoryServices.AccountManagement

Then, just plug this logic into my HandleAuthenticateAsync method:

const string LDAP_PATH = "EX://exldap.example.com:5555";
const string LDAP_DOMAIN = "exldap.example.com:5555";

using (var context = new PrincipalContext(ContextType.Domain, LDAP_DOMAIN, "service_acct_user", "service_acct_pswd")) {
    if (context.ValidateCredentials(username, password)) {
        using (var de = new DirectoryEntry(LDAP_PATH))
        using (var ds = new DirectorySearcher(de)) {
            // other logic to verify user has correct permissions

            // User authenticated and authorized
            var identities = new List<ClaimsIdentity> { new ClaimsIdentity("custom auth type") };
            var ticket = new AuthenticationTicket(new ClaimsPrincipal(identities), Options.Scheme);
            return Task.FromResult(AuthenticateResult.Success(ticket));
        }
    }
}

// User not authenticated
return Task.FromResult(AuthenticateResult.Fail("Invalid auth key."));
Window
  • 1,189
  • 1
  • 14
  • 22
  • 1
    Hi @ntino, I realise this is fairly old, but what would "LDAP_PATH" look like in your example? (very new to AD, sorry.) Cheers. – Deadlykipper Jun 12 '18 at 08:48
  • 2
    @Deadlykipper Updated the answer to include examples of both `LDAP_DOMAIN` and `LDAP_PATH`. Cheers. – Window Jun 12 '18 at 18:49
  • Hi, is this supported in .net core app hosted in Linux? I need to do AD authenication in .net core application hosted in Linux. – Priya Aug 07 '18 at 14:48
  • @Priya I'm not 100% sure. Up until this morning I was under the impression that everything in .NET Core was 100% cross-platform-compatible, but I read an article that said that WCF support was to come out in 3.0, but would only be supported on Windows. I'm sorry, but I can't give you clear answer. I would suggest trying it out yourself to see if it works. – Window Aug 09 '18 at 21:45
  • 1
    Hi, I got it working with Novell.Ldap. Till date System.DirectoryServices is not working in Linux/Unix environment. I thought the same that everything written in .NET Core is platform independent. But not yet. Anyway, Novell.Ldap can be used for the same. – Priya Aug 10 '18 at 16:00
  • From where do you get username and password in `HandleAuthenticateAsync`? – Buchter Jun 27 '19 at 07:55
  • @Priya, I tried connecting to the AD using this library. The Bind method throws an exception for invalid credentials. The credentials are correct. I even tried passing in the Domain. Do you have any sample along with AD configurations settings? – Charanraj Golla Jul 02 '19 at 07:24
  • 3
    What's the difference between ("service_acct_user", "service_acct_pswd") and (username, password) ? – Bruno Santos Jul 03 '19 at 19:39
  • 1
    @BrunoSantos the "service_acct_user" and "service_acct_pswd" strings are the username and password of the service account you are actively using. This service account isn't the one that you're trying to validate. (username, password) are the username and password of the user who is requesting to be authenticated. – Window Jul 26 '19 at 18:07
  • @Window how do the strings 'service_acct_user' and 'service_acct_pswd' actually look like? Is it in the form of 'cn=read-only-admin,dc=example,dc=com' and 'password', or is it something else ? – Dejan Bogatinovski Aug 07 '19 at 16:46
  • 2
    @DejanBogatinovski This is code from a former company that I don't work at anymore so, I don't have access to it. But, I'm pretty sure they were just the username and password with no other text included. Ex: `service_acct_user = "Window"` `service_acct_pswd = "un1c0rn5!"` – Window Aug 07 '19 at 17:35
  • I am getting an error CS0117: "Options" have no "Scheme" – TK-421 May 14 '21 at 07:55
37

According to #2089, it is only available in Windows Compatibility-Pack for .NET Core. I currently use Novell.Directory.Ldap.NETStandard.

public bool ValidateUser(string domainName, string username, string password)
{
   string userDn = $"{username}@{domainName}";
   try
   {
      using (var connection = new LdapConnection {SecureSocketLayer = false})
      {
         connection.Connect(domainName, LdapConnection.DEFAULT_PORT);
         connection.Bind(userDn, password);
         if (connection.Bound)
            return true;
      }
   }
   catch (LdapException ex)
   {
      // Log exception
   }
   return false;
}

For authentication and authorization, we can use Cookie Authentication Middleware with claims.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
   ILoggerFactory loggerFactory)
{
   app.UseCookieAuthentication(new CookieAuthenticationOptions
   {                
      AuthenticationScheme = "AuthenticationScheme",
      LoginPath = new PathString("/Account/Login"),
      AccessDeniedPath = new PathString("/Common/AccessDenied"),
      AutomaticAuthenticate = true,
      AutomaticChallenge = true
   });
}

It has few moving pieces, so I created a working sample project at GitHub. There are two main pieces - LdapAuthenticationService and SignInManager.

Win
  • 56,078
  • 13
  • 92
  • 167
2

The LDAP Authentication can be achieved using System.DirectoryServices.Protocols namespace.

public Boolean IsAuthenticated(string username, string password,string domain)
{
    Boolean authenticated = false;
    //pass the connectionString here
    using (LdapConnection connection = new LdapConnection(connectionString))
    {
       try
       {
           username = username + domain;
           connection.AuthType = AuthType.Basic;
           connection.SessionOptions.ProtocolVersion = 3;
           var credential = new NetworkCredential(username, password);
           connection.Bind(credential);
           authenticated = true;
           return authenticated;
       }
       catch (LdapException)
       {
           return authenticated;
       }
       finally
       {
           connection.Dispose();
       }
   }}
Dan
  • 47
  • 3
  • I think System.DirectoryServices.Protocols.LdapConnection only applies to regular .Net Framework, while the question was for .Net Core / .Net Standard. Correct me if I'm wrong but I highly doubt this assembly exist in those frameworks flavors. – AFract Dec 09 '19 at 17:03
  • Does not work on Linux. Calls to System.DirectoryServices will throw a PlatformNotSupported exception. – norgie Jan 23 '20 at 14:05
  • 1
    Does not work on Windows using .net core 3.1. Calls to System.DirectoryServices.Protocols throws PlatformNotSupported. – Matt R Sep 08 '20 at 17:48
2
namespace: System.DirectoryServices.AccountManagement;



 public bool UserAuthentication(string username, string password)
 {
        PrincipalContext pc = new PrincipalContext(ContextType.Domain, "<DOMAIN NAME>");
        bool isValid = pc.ValidateCredentials(username, password);
        return isValid;
 }
Adeel Rana
  • 25
  • 8