Authenticating Web Users Transparently
against NTLM with ASP Script and ADSI

By Peter A. Bromberg, Ph.D.

Peter Bromberg

Recently our company was working on a custom authentication scheme for users of a web - based application. It was suggested that we use the native NTLM user database to make an additional security check for the credentials of incoming users. However there were two important differences:

1) All users log on using a custom component, and their username and password are transmitted as elements of an authentication XML document that is passed to a service provider component. They are running their browsers under the IUSER account on the main webserver, so traditional browser authentication methods couldn't be used.



2) We didn't want to have to pop up a native NT login dialog box in order to force login to check the user against the NT User database since they had already "logged in" under our custom component. That would be overkill.

There was much discussion about purchasing various COM components to do the authorization, but since the application is to be distributed to many customers, this would mean the additional expense of component licenses for each customer. Was there another solution?

I remembered having played with ADSI 2.5 under NT 4.0, and did some quick investigation. Here is the solution I came up with. While this may not work for everyone in all situations, it gave us a pretty strong way to use NTLM to authenticate users transparently when their credentials can't be verified using normal methods.

Authenticating and changing the User Password

We gain access to the ADSI User object interface via the "GetObject" method. The ASP script I present following is a "test harness" to illustrate the methods involved via a form post. In real production, you would probably make this into an authentication function to which you pass the XML node values or variables containing the user's UserName and Password, and the function would simply return "true" if they were authenticated, and "false" if not. Since my script is fairly well documented, let's skip the talk and get right into the code:

               
  <%
   '''''''''''''''''''''''''''''''''''''''''''''''''
   ' ADSI User Authentication Script
   ' Peter A. Bromberg  01/26/2001
   '''''''''''''''''''''''''''''''''''''''''''''''''
    ' Test here for a form post ...
	if request("GETUSER") = "" Then
    ' write out the test form 
		With Response
		.write "<FORM ACTION=USER.ASP METHOD=POST>"
		.Write "<INPUT TYPE=TEXT NAME=oDomain>ENTER DOMAIN<BR>"
		.write "<INPUT TYPE=TEXT NAME=oUSer>ENTER USER NAME TO CHECK<BR>"
		.write "<INPUT TYPE=PASSWORD NAME=oPassword>ENTER USER PASSWORD<BR>"
		.Write "<INPUT TYPE =SUBMIT NAME=GETUSER VALUE=CHECK>"
		.write "</FORM>"
		end with
	
	else
   ' Form was autopostback, grab the form variables ...
		  oDomain= Request("oDomain")
		  oUser = Request("oUser")
		  oPassword = Request("oPassword")
' begin "Kludge" VBScript error trapping  (see Javascript version for
 try / catch handling)
on error resume next
' Set reference to the ADSI interface to NT User Manager ...
Set objUser = GetObject("WinNT://" & oDomain & "/" & oUser )

	if err.number <> 0 then
		Response.write "Login Error---"
		Response.end
	end if
	If len(objUser.FullName) < 1 then
			response.write "User Not Found!!!!"
			response.end
	else

on error resume next
' We verify the user password by using the ChangePassword method to change 
' the password back to itself.
' Since this requires that first parameter password be correct, it's an easy
' way to circumvent the fact ' that NT won't give actual access to the user's password to make a comparison.
' Be aware that on some systems, ' Admin policy settings may force the password ' to have to be changed after X number of uses ' of this method...
objUser.ChangePassword oPassword, oPassword if err.number <> 0 then Response.write" BAD PASSWORD!" Response.end else
' Now write out the ADSI User object properties that are supported by Windows 2000 ...
With Response .write "USER AUHTENTICATED!<BR>" .write "Properties for user " & objUser.FullName & ": <BR>" .Write "AccountExpirationDate: " & objUser.AccountExpirationDate & "<BR>" .Write "BadLoginCount: " & objUser.BadLoginCount & "<BR>" .write "Description: " & objUser.Description & "<BR>" .write "HomeDirectory: " & objUser.HomeDirectory & "<BR>" .write "IsAccountLocked: " & objUser.IsAccountLocked & "<BR>" .write "LastLogin: " & objUser.LastLogin & "<BR>" .write "LastLogoff: " & objUser.LastLogoff & "<BR>" .write "LoginHours: " & objUser.LoginHours & "<BR>" .write "LoginScript: " & objUser.LoginScript & "<BR>" .write "LoginWorkstations: " & objUser.LoginWorkstations & "<BR>" .write "MaxStorage: " & objUser.MaxStorage &"<BR>" .write "PasswordExpirationDate: " & objUser.PasswordExpirationDate & "<BR>" .write "PasswordMinimumLength: " & objUser.PasswordMinimumLength & "<BR>" .write "PasswordRequired: " & objUser.PasswordRequired & "<BR>" .write "Profile: " & objUser.Profile & "<BR>" .write "Account Disabled: " & objUser.AccountDisabled end with end if end if end if %>

Authentication With UserName / Password Pair Using the OpenDSObject Method

Now lets bind to the ADSI User Object using the OpenDSOObject method, which allows us 
to directly authenticate the user:
<%@ Language = VBScript %>
<html>
<head>
<title> ASP Authentication Page </title>
</head>
<body>

<h1> ASP Authentication page </h1>
<%
on error resume next
Dim strADsPath 
strADsPath = Request.Form("ADsPath")
Dim strUserName 
strUserName = Request.Form("UserName")
Dim strPassword 
strPassword = Request.Form("Password")
Dim iFlags 
iFlags = Request.Form("Flags")
%>

This page will attempt to authenticate to the ADSI object supplied using the
credentials you enter here.<br>
<form action = "Authenticate.asp" method = "post" id=frmAuth1 name=frmAuth1>
ADSI Object <INPUT type="text" id=ADsPath name=ADsPath size = 60 value = 
<% Response.Write strADsPath %> > :e.g.: WinNT://Domain<br>
Your UserName<INPUT type="text" id=UserName name=UserName size = 60 value = 
<% Response.Write strUserName%> > e.g.: Domain\UserName<br>
Your Password<INPUT type="password" id=Password name=Password size = 20 value = 
<% Response.Write strPassword%> ><br>
Flags (integer)<INPUT type="text" id=Flags name=Flags size = 10 value = 0>

e.g. 1 = Secure Authentication Flag<br> <INPUT type="submit" value="Submit" id=submit1 name=submit1> <INPUT type="reset" value="Reset" id=reset1 name=reset1><br> </form> <% if (not strADsPath= "") then ' bind to the ADSI object and authenticate Username and password Dim oADsObject Set oADsObject = GetObject(strADsPath) response.write "Authenticating<br>" Dim strADsNamespace Dim oADsNamespace strADsNamespace = left(strADsPath, instr(strADsPath, ":")) set oADsNamespace = GetObject(strADsNamespace) Set oADsObject = oADsNamespace.OpenDSObject(strADsPath, strUserName, strPassword, 0) ' we're only bound if err.number = 0 if not (Err.number = 0) then Response.Write "Failed to bind to object <b>" & strADsPath & "</b><br>" response.write err.description & "<p>" Response.write "Error number is " & err.number & "<br>" else Response.Write "USER AUTHENTICATED!" Response.Write "Currently viewing object at <b>" & oADsObject.ADsPath & "</b><br>" Response.Write "Class is " & oADsObject.Class & "<br>" end if response.write "<p>" end if %> </body> </html>

... And there you have it! Transparent user NTLM authentication including the ability to change the user password through script, without that annoying Popup login dialog box! In the downloadable source, you will find the above ASP scripts as well as a sister script to the first one, but written in Javascript.

 

Download the code for this article

Peter Bromberg is an independent consultant specializing in distributed .NET solutions in Orlando and a co-developer of the NullSkull.com developer website. He can be reached at info@eggheadcafe.com