It has been asked, and answered for .NET, but now it\'s time to get an answer for native Win32 code:
How do i validate a Windows username and password?
I might as well post the native code to validate a set of Windows credentials. It took a while to implement.
function TSSPLogon.LogonUser(username, password, domain: string; packageName: string='Negotiate'): HRESULT;
var
ss: SECURITY_STATUS;
packageInfo: PSecPkgInfoA;
cbMaxToken: DWORD;
clientBuf: PByte;
serverBuf: PByte;
authIdentity: SEC_WINNT_AUTH_IDENTITY;
cbOut, cbIn: DWORD;
asClient: AUTH_SEQ;
asServer: AUTH_SEQ;
Done: boolean;
begin
{
If domain is blank will use the current domain.
To force validation against the local database use domain "."
sspiProviderName is the same of the Security Support Provider Package to use. Some possible choices are:
- Negotiate (Preferred)
Introduced in Windows 2000 (secur32.dll)
Selects Kerberos and if not available, NTLM protocol.
Negotiate SSP provides single sign-on capability called as Integrated Windows Authentication.
On Windows 7 and later, NEGOExts is introduced which negotiates the use of installed
custom SSPs which are supported on the client and server for authentication.
- Kerberos
Introduced in Windows 2000 and updated in Windows Vista to support AES) (secur32.dll)
Preferred for mutual client-server domain authentication in Windows 2000 and later.
- NTLM
Introduced in Windows NT 3.51 (Msv1_0.dll)
Provides NTLM challenge/response authentication for client-server domains prior to
Windows 2000 and for non-domain authentication (SMB/CIFS)
- Digest
Introduced in Windows XP (wdigest.dll)
Provides challenge/response based HTTP and SASL authentication between Windows and non-Windows systems where Kerberos is not available
- CredSSP
Introduced in Windows Vista and available on Windows XP SP3 (credssp.dll)
Provides SSO and Network Level Authentication for Remote Desktop Services
- Schannel
Introduced in Windows 2000 and updated in Windows Vista to support stronger AES encryption and ECC (schannel.dll)
Microsoft's implementation of TLS/SSL
Public key cryptography SSP that provides encryption and secure communication for
authenticating clients and servers over the internet. Updated in Windows 7 to support TLS 1.2.
If returns false, you can call GetLastError to get the reason for the failure
}
// Get the maximum authentication token size for this package
ss := sspi.QuerySecurityPackageInfoA(PAnsiChar(packageName), packageInfo);
if ss <> SEC_E_OK then
begin
RaiseWin32Error('QuerySecurityPackageInfo "'+PackageName+'" failed', ss);
Result := ss;
Exit;
end;
try
cbMaxToken := packageInfo.cbMaxToken;
finally
FreeContextBuffer(packageInfo);
end;
// Initialize authorization identity structure
ZeroMemory(@authIdentity, SizeOf(authIdentity));
if Length(domain) > 0 then
begin
authIdentity.Domain := PChar(Domain);
authIdentity.DomainLength := Length(domain);
end;
if Length(userName) > 0 then
begin
authIdentity.User := PChar(UserName);
authIdentity.UserLength := Length(UserName);
end;
if Length(Password) > 0 then
begin
authIdentity.Password := PChar(Password);
authIdentity.PasswordLength := Length(Password);
end;
AuthIdentity.Flags := SEC_WINNT_AUTH_IDENTITY_ANSI; //SEC_WINNT_AUTH_IDENTITY_UNICODE
ZeroMemory(@asClient, SizeOf(asClient));
ZeroMemory(@asServer, SizeOf(asServer));
//Allocate buffers for client and server messages
GetMem(clientBuf, cbMaxToken);
GetMem(serverBuf, cbMaxToken);
try
done := False;
try
// Prepare client message (negotiate)
cbOut := cbMaxToken;
ss := Self.GenClientContext(@asClient, authIdentity, packageName, nil, 0, clientBuf, cbOut, done);
if ss < 0 then
begin
RaiseWin32Error('Error generating client context for negotiate', ss);
Result := ss;
Exit;
end;
// Prepare server message (challenge).
cbIn := cbOut;
cbOut := cbMaxToken;
ss := Self.GenServerContext(@asServer, packageName, clientBuf, cbIn, serverBuf, cbOut, done);
if ss < 0 then
begin
{
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.
}
RaiseWin32Error('Error generating server message for challenge', ss);
Result := ss;
Exit;
end;
// Prepare client message (authenticate).
cbIn := cbOut;
cbOut := cbMaxToken;
ss := Self.GenClientContext(@asClient, authIdentity, packageName, serverBuf, cbIn, clientBuf, cbOut, done);
if ss < 0 then
begin
RaiseWin32Error('Error generating client client for authenticate', ss);
Result := ss;
Exit;
end;
// Prepare server message (authentication).
cbIn := cbOut;
cbOut := cbMaxToken;
ss := Self.GenServerContext(@asServer, packageName, clientBuf, cbIn, serverBuf, cbOut, done);
if ss < 0 then
begin
RaiseWin32Error('Error generating server message for authentication', ss);
Result := ss;
Exit;
end;
finally
//Free resources in client message
if asClient.fHaveCtxtHandle then
sspi.DeleteSecurityContext(@asClient.hctxt);
if asClient.fHaveCredHandle then
sspi.FreeCredentialHandle(@asClient.hcred);
//Free resources in server message
if asServer.fHaveCtxtHandle then
sspi.DeleteSecurityContext(@asServer.hctxt);
if asServer.fHaveCredHandle then
sspi.FreeCredentialHandle(@asServer.hcred);
end;
finally
FreeMem(clientBuf);
FreeMem(serverBuf);
end;
Result := S_OK;
end;
Note: Any code released into public domain. No attribution required.