The LogonUser API

The LogonUser API: ASP Alliance

Introduction

The Windows Authentication prompt can often be an intimidating dialog for users. It asks for two or three things: username, password, and sometimes domain. Users may (and should) know their network username and password combination, but how many of them know the name of the domain their account is kept on? To make matters more complex, depending on the operating system and browser version, the domain entry box isnt shown. In this case, users need to prefix their username with DomainName\:

Another solution to this is to use a standard WebForm in conjunction with a Windows API, LogonUser. The LogonUser API is a function found in the advapi32.dll file of All Windows NT, 2000, and newer based servers and workstations.

The LogonUser API

The LogonUser function is defined in the Microsoft Platform SDK as follows:

BOOL LogonUser(
LPTSTR lpszUsername,
LPTSTR lpszDomain,
LPTSTR lpszPassword,
DWORD dwLogonType,
DWORD dwLogonProvider,
PHANDLE phToken
);

This function takes five input parameters, and has a sixth output parameter, a token. A bit more on tokens will follow this. The .Net version of the API declaration is as follows (there are multiple ways to do this, all of which are functionally equivalent):

Private Declare Auto Function LogonUser Lib “advapi32.dll" ( _
ByVal lpszUsername As String, _
ByVal lpszDomain As String, _
ByVal lpszPassword As String, _
ByVal dwLogonType As Integer, _
ByVal dwLogonProvider As Integer, _
ByRef phToken As IntPtr) As Boolean

There are two input parameters which LogonUser requires that are of specific interest: dwLogonType, and dwLogonProvider. The two of these control the type of logon that is initiated, and the way the credentials are passed, respectively.

There are only a couple of logon types that really apply to this context: LOGON32_LOGON_NETWORK and LOGON32_LOGON_INTERACTIVE. If your need is solely to validate the supplied credentials, then LOGON32_LOGON_NETWORK is your best choice. It is the fastest, and does not cache the logins on the server. However, if you plan to impersonate the user whose credentials were supplied, LOGON32_LOGON_INTERACTIVE is necessary. It outputs the proper type of token for use with the WindowsIdentity.Impersonate method of the .Net Framework.

Access tokens are used to by the Windows Kernel to create processes as users, and do other security type things. There are various types of tokens which can be returned, depending on the logon type, and their specifics are beyond the scope of this article. Note, it is possible to use LOGON32_LOGON_NETWORK and use the access token to impersonate a user. Additional API calls are necessary to convert the returned token to the type used by the WindowsIdentity class.

dwLogonProvider dictates the method in which the web server will pass the users credentials to the domain. There are four possible values:

LOGON32_PROVIDER_DEFAULT
LOGON32_PROVIDER_WINNT35
LOGON32_PROVIDER_WINNT40
LOGON32_PROVIDER_WINNT50

The first of the four will use the default logon protocol for the system. By default on Windows 2000, this is NTLM, which will work with Windows NT 4.0 and newer domains. If one or more of the domain controllers youll be authenticating against is still running Windows NT 3.51, youll need to use LOGON32_PROVIDER_WINNT35. If the web server is running Windows XP or Windows Server 2003, and you have a Windows NT4 or NT351 based domain, youll need to use the appropriate provider specific to that type of domain, because the default provider is LOGON32_PROVIDER_WINNT50, which is not supported by Windows NT4.

One final parameter which has some special cases is the lpszDomain parameter. To authenticate against a domain, specify the NetBIOS name of the domain in which the account resides (this is the name selected in the dropdown of a Windows Control + Alt + Del when you login to your computer). If a . Is specified, the LogonUser API will attempt to login to the local machine (web server). The name of another workstation or member server on the network may also be specified for lpszDomain.

Code

With all that said and done, its time for some code! The download for this article contains a sample webform and the associated code in Visual Basic.Net and C#. All the code shown in the article is written in Visual Basic.Net.

The first step is to declare the necessary API call, and the constants associated with it:

Private Declare Auto Function LogonUser Lib “advapi32.dll" ( _
ByVal lpszUsername As String, _
ByVal lpszDomain As String, _
ByVal lpszPassword As String, _
ByVal dwLogonType As Integer, _
ByVal dwLogonProvider As Integer, _
ByRef phToken As IntPtr) As Boolean

Const LOGON32_LOGON_INTERACTIVE As Long = 2
Const LOGON32_LOGON_NETWORK As Long = 3
Const LOGON32_PROVIDER_DEFAULT As Long = 0
Const LOGON32_PROVIDER_WINNT50 As Long = 3
Const LOGON32_PROVIDER_WINNT40 As Long = 2
Const LOGON32_PROVIDER_WINNT35 As Long = 1

Step two, actually use the API that has been defined:

Private Function ValidateLogin( _
ByVal Username As String, _
ByVal Password As String, _
ByVal Domain As String) As Boolean

Dim token As IntPtr

If LogonUser(Username, _
Domain, _
Password, _
LOGON32_LOGON_INTERACTIVE, _
LOGON32_PROVIDER_DEFAULT, token) = True Then

Return True
Else
Return False
End If
End Function

In this example, Im using interactive login, to allow for easy use of the access token which is returned (and stored in the IntPtr variable token) if the API call is successful. In this example, I havent done anything with the access token which is returned, and I will not do so at all in this article.

In Conjunction with Built-in ASP.Net Authentication

Since the point of the LogonUser code is to replace the ugly Windows Auth dialog, Windows Authentication obviously isnt the solution, in terms of choosing an authentication provider, if any.

Forms authentication, on the other hand, does the trick. The page which calls the LogonUser API is a standard web form, and hence can be used for forms authentication. In the included demo files, forms authentication is demonstrated, in conjunction with the LogonUser API. The following line of code does the forms authentication:

FormsAuthentication.RedirectFromLoginPage(Username, False)

The first parameter specifies the username of the user being logged in, accessible after this via Context.User.Identity.Name, and the second parameter specifies whether or not a persistent cookie should be saved.

Handling Errors

If the API Call returns false, use this line of code to get the Win32 error code back for debugging:

System.Runtime.InteropServices.Marshal.GetLastWin32Error()

A listing of Win32 error codes can be found online at: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/system_error_codes.asp

Server Setup

This section applies only if youre running a Windows 2000 server. Windows XP, and Windows Server 2003 handle this behavior automatically, so no further configuration is necessary. The ASPNET account, or whatever account your server is configured to run the ASP.Net worker process account as requires the Act as Part of the Operating System user right. This is also known by the operating system, and in error messages as SE_TCB_NAME. Configuring this right is a cinch:

1. Logon to the server as an administrator
2. Open up the machines local security policy (start>run>secpol.msc)
3. Expand Security Settings, Local Policies, User Rights Assignment
4. Double click Act as part of the operating system
5. Click Add User or Group, enter the worker process account name and click OK, and OK again

In order for this change to become effective, the web server will have to be rebooted. This setting is applied when the start dialog reads Applying Security Settings.

From the Frontline

Ive used the LogonUser API in conjunction with two web applications already. It worked well. The reason I chose the API route is because of the user base Im targeting about 650 high-school students. The computers they were going to use to login were running two different operating systems, Windows NT4, and Windows 2000. The LogonUser API guaranteed me an interface which was conformant with the look of the rest of our homepage, and the ability to upgrade to Windows 2000 without changing a line of code.

I learnt the hard way that LOGON32_LOGON_INTERACTIVE is definitely not the provider to use when servicing a high volume of simultaneous login events. The first time I used the LogonUser API in a production app, I had about six-hundred users login over a thirty minute period. For reasons unbeknownst to me, the web server would reject valid credentials from time to time. It was as though the API call never executed because nothing was returned. Moral of this story, use LOGON32_LOGON_NETWORK for high load scenarios.

For my production environment, Ive encapsulated the LogonUser API call, along with several other related API calls for easy use from any web or windows application.

In Summary

For all the details of how to work with the LogonUser API call, its actually pretty simple to use. A few things to consider:

When deciding which logon provider to use, you must be sure that all of the domain controllers on the network support the protocol youre using. This means that if all but one of the domain controllers in your network is running Windows 2000, and this lone NT4 domain controller is located halfway across the globe, you still need to use the NT40 logon provider. This is because your web server could theoretically contact the NT4 domain controller, and fail to communicate the given credentials.

If you have no plans to impersonate the logged in user, use the LOGON32_LOGON_NETWORK login type. It is faster, and consumes less network resources.

If you have one account domain, I’d recommend that you store it in the appSettings area of your web.config file. That way, if your network admins ever rename the domain, you wont have to recompile the application. Obviously, if there are multiple account domains, your users or your application will have to decide which one to query.

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *

What is 4 + 12 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)