| 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 EggheadCafe.com
developer website. He can be reached at pbromberg@yahoo.com |