HOWTO: Validate User Credentials on Microsoft WinNT and Win95

ID: Q180548


The information in this article applies to:


SUMMARY

Occasionally you may want an application to verify a user's user name and password (hereafter referred to as credentials). You can do this a couple of different ways, depending on whether you are running the application on Windows 95 or Windows NT.

This article describes all of the common ways to verify a user's credentials and the special requirements for each method.

NOTE: Collecting user credentials from a User-mode application can be annoying to the users and can provide a possible security hole in the enterprise computing environment. The Unified Logon requirement (a requirement that the user should only have to type their credentials one time at the Ctl-Alt-Del screen of either Windows 95 or Windows NT), was added to the Microsoft BackOffice logo requirements for these very reasons. It is important to make sure that you really need to gather credentials and that some other method of client/server validation is not more appropriate. Consult the security documentation in the Platform SDK for more information on impersonation and programming secured servers.


MORE INFORMATION

The LogonUser API has been available and documented since Windows NT 3.51, and is commonly used to verify user credentials. Unfortunately, there are some restrictions on using LogonUser that are not always convenient to satisfy. The first and biggest of these restrictions is that the process calling LogonUser must have the SE_TCB_NAME privilege (in User Manager, this is the "Act as part of the Operating System" right). The SE_TCB_NAME privilege is very powerful and should not be granted to any arbitrary user just so that they can run an application that needs to validate credentials. The recommended method is to call LogonUser from a service running in the local system account since the local system account already has the SE_TCB_NAME privilege.

One other problem with LogonUser is that the API is not implemented on Windows 95.

As another option, you can use the Security Support Provider Interface (SSPI) to do a network style logon with provided user credentials. This method of validation has the advantage of not requiring any special privilege, as well as working on Windows 95. The end result of using the SSPI services to validate the credentials is a logon that is analogous to calling the LogonUser API with the LOGON32_LOGON_NETWORK logon type. The biggest downside to this type of logon is that you cannot access remote network resources after impersonating a network type logon. If your application is calling LogonUser with the LOGON32_LOGON_INTERACTIVE logon type to workaround Windows NT's inability to perform delegation, then the SSPI logon/validation will probably not be a viable alternative.

The sample code provided below shows how to call the SSPI services to perform credential validation. This is just a modification of the SOCKAUTH sample from the Platform SDK that uses the SSPI services. Two modules from that sample are necessary to compile the code below.

To use this sample on Windows 95, you need to load the Windows 95 version of the SSP package. The Windows 95 version of the SSP package is called Secur32.dll, rather than the Security.dll on Windows NT. You need to make this change in SECURITY.C for the NT_DLL_NAME constant value.

To use this method on Windows 95, you also need to enable the NTLM security services by opening Control Panel, Network, Access Control, and then selecting User-level access control.

Sample Code




   /*++ 



   Module Name: 


SSPLogon.c



   Abstract: 


This module implements the network logon type by interfacing with the Windows NT Lan Man Security Support Provider (NTLMSSP) for the purpose of validating the provided users credentials.





   Author: 


David Mowers (DaveMo) January 14, 1998



   The following modules from the SockAuth sample are required: 


security.c (modify according to comment below) collect.c



   The following import libraries are required: 


none



   Revision History: 



   --*/  



   #define SECURITY_WIN32 



   #include <windows.h>
   #include <sspi.h> 



   // 
   // Slight change to GenClientContext so that you can
   // pass user credentials.
   // 
   BOOL GenClientContext (
      DWORD dwKey,
      SEC_WINNT_AUTH_IDENTITY *pAuthIdentity,
      BYTE *pIn,
      DWORD cbIn,
      BYTE *pOut,
      DWORD *pcbOut,
      BOOL *pfDone); 



   /* 


In security.c, for the GenClientContext function, make the following modification:


ss = g_pFuncs->AcquireCredentialsHandle ( NULL, // principal PACKAGE_NAME, SECPKG_CRED_OUTBOUND, NULL, // LOGON id pAuthIdentity, // auth data NULL, // get key fn NULL, // get key arg &pAS->_hcred, &Lifetime );

   */  





   static PBYTE g_pClientBuf = NULL;
   static PBYTE g_pServerBuf = NULL;
   static DWORD g_cbMaxMessage = 0; 



   BOOL
   SSPLogonUser( 
LPTSTR DomainName, LPTSTR UserName, LPTSTR Password )

   { 


BOOL done = FALSE; DWORD cbOut, cbIn; char szUser[80]; DWORD cbUser = 80; SEC_WINNT_AUTH_IDENTITY AuthIdentity;


if(!InitSession(0)) { return(FALSE); }


if(!InitSession(1)) { return(FALSE); }


if (!InitPackage (&g_cbMaxMessage)) { return(FALSE); }


g_pClientBuf = (PBYTE) malloc(g_cbMaxMessage); g_pServerBuf = (PBYTE) malloc(g_cbMaxMessage);


ZeroMemory( &AuthIdentity, sizeof(AuthIdentity) );


if ( DomainName != NULL ) { AuthIdentity.Domain = DomainName; AuthIdentity.DomainLength = lstrlen(DomainName); }


if ( UserName != NULL ) { AuthIdentity.User = UserName; AuthIdentity.UserLength = lstrlen(UserName); }


if ( Password != NULL ) { AuthIdentity.Password = Password; AuthIdentity.PasswordLength = lstrlen(Password); }



   #ifdef UNICODE 
AuthIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;

   #else 
AuthIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;

   #endif 



      // 
      // Prepare client message (negotiate).
      // 
      cbOut = g_cbMaxMessage;
      if (!GenClientContext (
         0,
         &AuthIdentity,
         NULL,
         0,
         g_pClientBuf,
         &cbOut,
         &done))
      {
         return(FALSE);
      } 


cbIn = cbOut; // // Prepare server message (challenge). // cbOut = g_cbMaxMessage; if (!GenServerContext ( 1, g_pClientBuf, cbIn, g_pServerBuf, &cbOut, &done)) { // // Most likely failure: AcceptServerContext fails with // SEC_E_LOGON_DENIED in the case of bad username or password // // Unexpected Result: Logon will succeed if you pass in a bad // username and the guest account is enabled in the specified // domain. // return(FALSE); }


cbIn = cbOut; // // Prepare client message (authenticate). // cbOut = g_cbMaxMessage; if (!GenClientContext ( 0, &AuthIdentity, g_pServerBuf, cbIn, g_pClientBuf, &cbOut, &done)) { return(FALSE); }


cbIn = cbOut; // // Prepare server message (authentication). // cbOut = g_cbMaxMessage; if (!GenServerContext ( 1, g_pClientBuf, cbIn, g_pServerBuf, &cbOut, &done)) { return(FALSE); }


TermSession(0); TermSession(1);


TermPackage();


free(g_pClientBuf); free(g_pServerBuf);


return(TRUE);

   } 



   int main( int argc, char *argv[] )
   { 


if(argc<4) { printf( "Usage: %s <domain> <user> <password>\n", argv[0]); exit(0); }


if(SSPLogonUser( argv[1], argv[2], argv[3])) { printf("SSP Logon Succeeded!\n"); exit(1); } else { printf("SSP Logon Failed!\n"); exit(0); }



   } 

Additional query words:


Keywords          : kbAPI kbKernBase kbGrpKernBase 
Version           : winnt:
Platform          : winnt 
Issue type        : kbhowto 

Last Reviewed: June 15, 1999