Published: Wednesday, June 12, 2002
How to Validate a User Exists in a Windows NT Domain
By Douglas Setzer, II
Introduction
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
MyDomain:
'Specify the domain and username
strNTDomain = "MyDomain"
strUsername = "dsetzer"
'Get information about the user
Dim objIADSUser
Set objIADSUser = GetObject("WinNT://" & strNTDomain & "/" & strUsername & ",user")
|
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
the request: 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:
'specify the old and new passwords
strOldPassword = "THEPWD"
strNewPassword = "ILOVEMOM"
'change the user's password
objIADSUser.ChangePassword strOldPassword,strNewPassword
objIADSUser.SetInfo
|
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
under the 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:
'Specify the domain we're working with
strNTDomain = "MyDomain"
'Specify the username/password for the administrator account
strAdminUsername = "Administrator"
strAdminPassword = "password"
'Specify the name of the user whose password you wish to change
strClientUsername = "dsetzer"
'Specify the desired old and new password for the user
strOldPassword = "THEPWD"
strNewPassword = "ILOVEMOM"
'Create a container for the Administrator account
Const ADS_SECURE_AUTHENTICATION = 1
Set objIADS = GetObject("WinNT:").OpenDSObject("WinNT://" & _
strNTDomain, strAdminUserame, _
strAdminPassword, _
ADS_SECURE_AUTHENTICATION)
'Change the user's password through the Administrator container
Set objIADSUser = objIADS.GetObject("user", strClientUsername)
objIADSUser.ChangePassword strOldPassword,strNewPassword
objIADSUser.SetInfo
'Clean up
Set objIADSUser = Nothing
Set objIADS = Nothing
|
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!
Happy Programming!
By Douglas Setzer, II
27 Seconds, Inc., President
Attachments
Download the complete code for the COM+ Component (in ZIP format)