How to Validate a User Exists in a Windows NT DomainBy Douglas Setzer, II
In a recent project I worked on I needed to be able to verify that a Web visitor was indeed a user on our company's Windows NT domain. Everything that I read pointed me to use the Challenge/Response Authentication within IIS. If you've NT Challenge/Response for authentication you know that when an unauthenticated user visits a page that requires authorization the user is prompted with a login box asking for their username/password. (If you've not worked with NT Challenge/Response, be sure to read Using NT Challenge/Response and Authentication Methods in IIS.) The problem with the NT Challenge/Response login box is that I work with very particular graphic designers who have their own specific ideas on the way the login window should look. So I needed to be able to create a means to have a custom login Web page that collected a user's username and password and then determined if the entered credentials matched the credentials for a user on the Windows NT domain.
I found that ADSI (Active Directory Services Interface) seemed like it should be my answer. First a quick background on ADSI. ADSI services can be used to enumerate and manage resources in a directory service. Microsoft products that currently support the ADSI programming interface are Windows NT 4.0 Server, Windows 2000, Exchange, IIS, and Site Server. More ADSI information can be found at: ADSI Article Archive and the 15Second.com ADSI Focus Section.
Using ADSI to Retrieve Information
When using ADSI and the Windows NT provider, you are able to retrieve information about the Domain, Computers, Groups and Users. To get information about a particular user, you could use the following code, which retrieves information about the user
dsetzer on the NT Domain
The above code retrieves information about a specific user. Normally, you wouldn't hard-code the domain
and username values, but, for this example I do. The last line is the important one that does all the
work. It calls ADSI and creates an ADSI user object. Note that the variables get translated into
WinNT://MyDomain/dsetzer,user, and returns a User object.
Changing User Information through ADSI
The user object give you access to the user's information such as their full name, the groups they are in, if their account is disabled and if their account is locked out. In addition to finding out information about the user, it allows you to make changes to their account. To change their password, you execute the following code:
This code defines what the user's current password is and what the password should be changed to. It
then calls a method on the ADSI user object to change the password for the user. Anytime you make
changes, you need to call the
SetInfo method to do the actual update in the provider.
This is because ADSI locally caches the information about the user to speed up response times and
minimize network traffic.
In order for the change password code above to work, administrator rights are required. Because ADSI
was written with network administrators in mind, it gives you a way to run scripts on behalf of
another user. For instance, say a company wanted users to be able to change their password from the
company's intranet. Anonymous requests to the intranet are likely going to be configured to be run
IUSR_machinename account, which is generally a normal user with very
limited permissions. To allow for ADSI actions to occur for an intranet request, one can essentially
use ADSI to create a "container" object that encapsulates the Administrator account. Using this
container you can then change a user's password through an ASP Web page. Some code may help clarify things here:
While this code is a little longer that the previous example, it does, essentially, the same thing:
it changes a specified user's password from an old password to a new one. Recall that the script to
change the user's password must run under the Administrator account. Since anonymous Web requests are
treated as requests from the
IUSR_machinename account, to perform
an Administrator-level ADSI task through an ASP page you will need to create a container object that
impersonates the Administrator account. Once this container is created, you can use the container to
perform the password changing task.
Take a quick minute to examine the code. Note that there is a constant called
ADS_SECURE_AUTHENTICATION, which is used when authenticating the Administrator account.
Instead of creating the user object directly, as was done in the previous example, I first create a
container object on behalf of the Administrator. When I create this object, ADSI securely verifies
that the Administrator user exists in the domain, that the password matches, and that the account
isn't disabled or locked out. Using that container, I create a User object with which I'm then able
to change the client user's password using the same code as before.
Authenticating a User with ADSI
Changing a password is all fine and dandy, but, you are probably saying that you don't want to change the password, you want to check if a given username/password that has been entered into a login form matches the user's account on the Windows domain. As I mentioned earlier, with ADSI you can get various pieces of information about a user once you create an ADSI user object. One piece of information you can't get the user's password from this object -- even as an administrator. This is because when you enter or change your password in Windows, it doesn't store what you type in. Instead, it encrypts the password using a one-way hash encryption and stores the encrypted value. It can't tell you what the password is because it can't decrypt it. But, you aren't really interested in what the user's password is. Instead, you want to know that it matches whatever the user enters as his or her password.
To do this, you need to be able to create an object that verifies that the account exists, the
password matches and that it isn't disabled or locked out. Sound familiar? The ability to run
scripts on behalf of another user isn't limited to just Administrators. If you are able to create
the container object, without an error, you know that the account exists, the password matches and
that it isn't disabled or locked out. If you receive an error, you can check the
Err.Description property to find out why it failed.
In my particular implementation of this, I created a COM+ object
in VB6 that had a
UserLogin method that I passed a username, password and domain name. If it returned
True, I knew the account was good, if it returned
False, I knew the account
didn't exist. I set the COM+ object to be ran under an Administrator account. (You can
download the complete code for the COM+ component.)
This code has been tested on Windows 2000. I used Component Services to register the COM+ object and within that, I specified that the object run under an Administrator account. From the testing that I have done, this is the only way that I've gotten this to work. I haven't tested it, but I think the code would work directly from ASP if the virtual directory was configured to run under an administrator account.
A final note, you should definitely be using SSL to secure the transmission of the username and password from the browser to the server. Windows NT passwords are one thing that you don't want to let loose!
27 Seconds, Inc., President