Active Directory "login" from C# application

B

Benjamin de Waal

Hi all,
I am writing a program that needs to take a login name and password,
and then try to authenticate against an AD server. Sounds simple
enough so far...

The name and password may be entered by a user (real human being),
however it also may be generated by another application (actually, a
hardware device that my application is communicating with) that I have
no real control over. This other application always generates the
"account name" for Active Directory users (so, if the user is First
Name = "Ben", Last Name = "de Waal", Display Name = "Ben de Waal", but
login name = "bdw123", then the value that is passed is "bdw123").
Using:
DirectoryEntry("LDAP://" + adServer, userName, userPass, authType)
, it seems the userName should be "Ben de Waal" (from the above
example) to be accepted - if I try "bdw123", it fails to authenticate.
I was considering simply iterating through the user list and then
getting the "Ben de Waal" value from the "bdw123" user, however it was
pointed out to me (quite rightly) that you could have two users named
"Ben de Waal", but with different account names. This would mean that
the login attempt may then try the incorrect one, which would be very
bad.

Can anyone point me in the right direction for checking the "account
name"/password against AD rather than the "user name"/password?

Best regards,
Ben de Waal
 
W

Willy Denoyette [MVP]

Benjamin de Waal said:
Hi all,
I am writing a program that needs to take a login name and password,
and then try to authenticate against an AD server. Sounds simple
enough so far...

The name and password may be entered by a user (real human being),
however it also may be generated by another application (actually, a
hardware device that my application is communicating with) that I have
no real control over. This other application always generates the
"account name" for Active Directory users (so, if the user is First
Name = "Ben", Last Name = "de Waal", Display Name = "Ben de Waal", but
login name = "bdw123", then the value that is passed is "bdw123").
Using:
DirectoryEntry("LDAP://" + adServer, userName, userPass, authType)
, it seems the userName should be "Ben de Waal" (from the above
example) to be accepted - if I try "bdw123", it fails to authenticate.
I was considering simply iterating through the user list and then
getting the "Ben de Waal" value from the "bdw123" user, however it was
pointed out to me (quite rightly) that you could have two users named
"Ben de Waal", but with different account names. This would mean that
the login attempt may then try the incorrect one, which would be very
bad.

Can anyone point me in the right direction for checking the "account
name"/password against AD rather than the "user name"/password?

Best regards,
Ben de Waal


If the "User Logon Name" for this Account is "bdw123" and you have specified
the *userName* like this:
"bdw123@YourFQDN" or this "domainName\\bdw123" then the authentication
should succeed.

Note that using DirectoryEntry is not the most appropriate way to
authenticate Windows users, the AD is not an authentication service! You
should definitely look at the lower level LDAP services offered by V2 of the
Framework.

When binding to an instance of DirectoryEntry you are effectively performing
two actions:
1. authenticate the user using the credentials specified in the
DirectoryEntry contructor, and
2. perform a "read" authorization check against the AD object specified in
the DirectoryEntry contructor

A distinct exception will be thrown for 1 and 2, be careful to handle the
right exception.

A more appropriate way to authenticate a Windows user is by using
System.DirectoryServices.Protocols.
Here's a sample, to get you started.

using System.DirectoryServices.Protocols ;
....

using (LdapConnection ldap = new LdapConnection("ADServerName"))
{
ldap.AuthType = AuthType.Ntlm; // or Kerberos...
ldap.Bind(new NetworkCredential("bdw123", "hisPwd",
"domainName"));
}

Willy.
 
J

Joseph Daigle

In Active Directory, the user attribute used for authentication is the
sAMAccountName. When trying to authenticate using DirectoryEntry this is the
attribute you would use (in your example "bdw123"). If you're having
trouble getting a response from AD, try also using the overloaded
constructor for DirectoryEntry which does not specify authentication type.
 
J

Joseph Daigle

I would argue that AD is an authentication service with its core usage.

--
Joseph Daigle

Willy Denoyette said:

Note that using DirectoryEntry is not the most appropriate way to
authenticate Windows users, the AD is not an authentication service! You
should definitely look at the lower level LDAP services offered by V2 of
the Framework.

<snip>
 
W

Willy Denoyette [MVP]

Joseph Daigle said:
I would argue that AD is an authentication service with its core usage.

--

I know what you mean, but, it's not because you can "authenticate" a
*Windows* user by connecting to SQL Server, that you will start to use SQL
server as an authentication Service, isn't it ;-).
AD is a Directory Service, in order to perform access checks to it's
Directory Objects it must obtain an access token, this token is obtained as
a result of an authentication handshake with the LSA using the credentials
specified by the client. The LSA used to authenticate is not necessarily the
LSA running on the AD you are binding to.
But this is less important, the fact that you are using a "heavy weight"
service (the AD schema is transferred to the client for each new bind!)
just to authenticate a user, is what makes me say that it's not an
authentication service.
V2 of the framework includes "System.DirectoryServices.Protocols" which
offers a more " light weight" and easier way to authenticate *Windows* (or
non Windows) users (see my other reply) using .NET, but even this one is not
the "cheapest/fastest" way to authenticate a Windows user.
Here also, you are connecting to an LDAP service, which at his turn needs to
connect with the LSA, in order to authenticate a user, though, no further
request is made to the LDAP service. This is not the main purpose of LDAP
isn't it?


Willy.
 
W

Willy Denoyette [MVP]

Willy Denoyette said:
I know what you mean, but, it's not because you can "authenticate" a
*Windows* user by connecting to SQL Server, that you will start to use SQL
server as an authentication Service, isn't it ;-).
AD is a Directory Service, in order to perform access checks to it's
Directory Objects it must obtain an access token, this token is obtained
as a result of an authentication handshake with the LSA using the
credentials specified by the client. The LSA used to authenticate is not
necessarily the LSA running on the AD you are binding to.
But this is less important, the fact that you are using a "heavy weight"
service (the AD schema is transferred to the client for each new bind!)
just to authenticate a user, is what makes me say that it's not an
authentication service.
V2 of the framework includes "System.DirectoryServices.Protocols" which
offers a more " light weight" and easier way to authenticate *Windows* (or
non Windows) users (see my other reply) using .NET, but even this one is
not the "cheapest/fastest" way to authenticate a Windows user.
Here also, you are connecting to an LDAP service, which at his turn needs
to connect with the LSA, in order to authenticate a user, though, no
further request is made to the LDAP service. This is not the main purpose
of LDAP isn't it?


Willy.

Note that V3.5 has System.DirectoryServices.AccountManagement is even more
suitable to authenticate account credentials.

Willy.
 
J

Joseph Daigle

You're absolutely right, and I was fishing to bring this up. Since this
method of authentication is LDAP binding (and not necessarily dependant on
AD's implementation), using the more general Protocols namespace is the
"correct" approach.

Eitherway, unless your app is really hurting for performance, using
DirectoryServices isn't necessarily "bad" as much as it's not the ideal way
to authenticate.
 
W

Willy Denoyette [MVP]

Joseph,
It's not only about "hunting" for performance, it's about wasting precious
resources like network and AD server resources.
In a large corporations (like our), it's even not allowed to authenticate
against the AD (that is using ADSI), the network overhead(a result of the
schema data transfer) and the AD server resource consumption is way too huge
to be used at a "large scale". In some corporations, even not allowed for a
normal user to access the AD, only "DomainAdmins" are allowed to access the
directory.

Using SDS.P is better suited for the job, but as I said, even then some
corporate security regulations may prohibit LDAP traffic, in that case you
are force to use the SSPI API's which aren't directly exposed by the
Framework, but there is a way to use them to authenticate Windows users via
the NegotiateStream class :).

Willy.
 
J

Joseph Daigle

That is a good point. I often forget how large some organizations are, as
these issues really don't show up at all on the very small scale (2 servers,
less than 200 users) in my experiences.
 
B

Benjamin de Waal

A more appropriate way to authenticate a Windows user is by using
System.DirectoryServices.Protocols.

Hi Willy,
Thanks for the feedback. I tested my old (bad) method with the
"full" account name (including the @) and it worked. But due to there
being no simple way to definitively determine this full account name
in my application's situation, I decided it would be better to use the
method you suggest here (and it seems more technically proper as
well). Doing so, unfortunately has lead me to my next hurdle.
Using the (old/bad) DirectoryEntry method, it was then only a
trivial step to my next bit of code which reads the user's home
directory and email properties. With this new method, I am totally
lost as to how I'd go about this, as I don't have any kind of object
for the user at this point. Is there some method that I should now
call, which will give me the user object that I can just pull
properties from (in a similar fashion to SearchResult with
DirectorySearcher(directoryEntry))?
I pored over the MSDN pages and Google, but unfortunately without
knowing exactly what I'm looking for, I can't really find anything.

Best regards,
Ben de Waal
 
W

Willy Denoyette [MVP]

Benjamin de Waal said:
Hi Willy,
Thanks for the feedback. I tested my old (bad) method with the
"full" account name (including the @) and it worked. But due to there
being no simple way to definitively determine this full account name
in my application's situation, I decided it would be better to use the
method you suggest here (and it seems more technically proper as
well). Doing so, unfortunately has lead me to my next hurdle.
Using the (old/bad) DirectoryEntry method, it was then only a
trivial step to my next bit of code which reads the user's home
directory and email properties. With this new method, I am totally
lost as to how I'd go about this, as I don't have any kind of object
for the user at this point. Is there some method that I should now
call, which will give me the user object that I can just pull
properties from (in a similar fashion to SearchResult with
DirectorySearcher(directoryEntry))?
I pored over the MSDN pages and Google, but unfortunately without
knowing exactly what I'm looking for, I can't really find anything.

Best regards,
Ben de Waal


Ben,

Your old method is not "bad", it's not the most appropriate method if you
only need to authenticate a windows user.
However, you also need to query the directory, so your old method is not bad
at all, the only problem is that System.DirectoryServices.DirectoryEntry
wraps ADSI which is another layer to cross before getting at the AD. So, you
can continue to use System.DirectoryServices.DirectoryEntry, or you can
move to a lower level and skip ADSI altogether by using
System.DirectoryServices.Protocols, which is somewhat more complex
(depending on your level of expertise)
Another option is using System.DirectoryServices.AccountManagement, this
requires V3.5 of the framework.

Following is a (complete) sample that uses the
System.DirectoryServices.Protocols namespace to query the AD for a number of
user properties....

using System;
using System.DirectoryServices.Protocols;
using System.Net;
using System.Collections;
public class Program
{
static void Main()
{
using (LdapConnection ldap = new LdapConnection("yourDCName"))
{
// use NTLM as authentication protocol
ldap.AuthType = AuthType.Ntlm;
// binding user, can be any existing user with appropriate
rights to query the Directory!
ldap.Bind(new NetworkCredential("someuser", "pwd",
"someDomain"));
// search for user "testuser" in "testou" and return only
the properties cn, name, etc...
SearchRequest req = new SearchRequest("cn=testuser,
ou=testou, DC=....., DC=...., DC=....",
"(&(objectClass=user))", SearchScope.Subtree, new
string[]
{
"cn",
"name",
"canonicalName",
"userPrincipalName",
"homeDirectory",
"mail"});
SearchResponse resp = ldap.SendRequest(req) as
SearchResponse;
SearchResultEntryCollection col = resp.Entries;
foreach(SearchResultEntry ent in col)
{
Console.WriteLine(ent.DistinguishedName);
SearchResultAttributeCollection attCol = ent.Attributes;
foreach(DictionaryEntry att in attCol)
{
Console.Write(((DirectoryAttribute)att.Value).Name);
Console.WriteLine(" = {0} ",
((DirectoryAttribute)att.Value)[0]);
}
}
}
}
}

Willy.
 
S

stv.dmsk

Joseph,
It's not only about "hunting" for performance, it's about wasting precious
resources like network and AD server resources.
In a large corporations (like our), it's even not allowed to authenticate
against the AD (that is using ADSI), the network overhead(a result of the
schema data transfer) and the AD server resource consumption is way too huge
to be used at a "large scale". In some corporations, even not allowed for a
normal user to access the AD, only "DomainAdmins" are allowed to access the
directory.

Using SDS.P is better suited for the job, but as I said, even then some
corporate security regulations may prohibit LDAP traffic, in that case you
are force to use the SSPI API's which aren't directly exposed by the
Framework, but there is a way to use them to authenticate Windows users via
the NegotiateStream class :).

Willy.








- Show quoted text -

Hi,

It might be too late but as you are talking about large corporation,
you might wanna take a look at this product:

http://www.visual-guard.com/EN/.net...ment-access-control-authentication-tool#admin
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top