Check if user authentication in Active DIrectory
Asked Answered
J

3

7

i would like to know whether a user inputs the correct combination of Domain, User and Password for his Active Directory user.

I tried to make a very simple program that is not able to connect but by reading the error message i can know if the user/password is correct.

This is trick based (the logic is on reading the Exception message), anyway i testd this prototype on 2 servers and i noticed that the excpetion messages change from server to server so this is not reliable.

uses adshlp, ActiveDs_TLB;
// 3 TEdit and a TButton

procedure TForm4.Button1Click(Sender: TObject);
Var
 aUser : IAdsUser;
 pDomain, pUser, pPassword : string;
 myResult : HRESULT;
 Counter: integer;
begin
  pDomain := edtDomain.Text;
  pUser:= edtUser.Text;
  pPassword := edtPwd.Text;
  Counter := GetTickCount;


 Try
    myResult := ADsOpenObject(Format('LDAP://%s',[pDomain]),Format('%s\%s',[pDomain,pUser]),pPassword,
    ADS_READONLY_SERVER,
    IAdsUser,aUser);
 except
    On E : EOleException do
    begin
    if (GetTickCount - Counter > 3000) then  ShowMessage ('Problem with connection') else
    if Pos('password',E.Message) > 0  then ShowMessage ('wrong username or password') else
     if Pos('server',E.Message) > 0 then ShowMessage ('Connected') else
     ShowMessage('Unhandled case');
    memLog.Lines.Add(E.Message);
    end;

 end
end;

The reason why i set "Connected" if the message contain "server" is that on my local machine (on my company ldap server in fact) in case all is fine (domain, user and password) the server replies "The server requires a safer authentication", so the "server" word is in there, while in other cases it says "wrong user or password". SInce this must work on itlian and english servers i set "server" and "pasword" as reliable words. Anyway i tested on another server that gives differente errors.

I started from a reply to this question to do the above.

How can i check if the user set the correct password or not in a more reliable way using a similar technique?

UPDATE (found solution)

Thanks to the replies i managed to write this function that does what i need. It seems quite reliable up to now, I write here to share, hoping it can help others:

// This function returns True if the provided parameters are correct
// login credentials for a user in the specified Domain
// From empirical tests it seems reliable
function UserCanLogin(aDomain, aUser, aPassword: string): Boolean;
var
  hToken: THandle;
begin
  Result := False;
  if (LogonUser(pChar(aUser), pChar(aDomain), pChar(aPassword), LOGON32_LOGON_INTERACTIVE,
   LOGON32_PROVIDER_DEFAULT, hToken)) then
  begin
    CloseHandle(hToken);
    Result := True;
  end;
end;
Jory answered 10/5, 2017 at 15:6 Comment(0)
K
2

i would like to know whether a user inputs the correct combination of Domain, User and Password for his Active Directory user.

You can use LogonUser function to validate user login e.g. :

  if (LogonUser(pChar(_Username), pChar(_ADServer), pChar(_Password), LOGON32_LOGON_INTERACTIVE,
   LOGON32_PROVIDER_DEFAULT, hToken)) then
  begin
    CloseHandle(hToken);
    //...
    //DoSomething
  end
  else raise Exception.Create(SysErrorMessage(GetLastError));

Note: You must use LogonUser within a domain machine to be able to use the domain login or the function will always return The user name or password is incorrect

The alternative is using TLDAPSend e.g. :

function _IsAuthenticated(const lpszUsername, lpszDomain, lpszPassword: string): Boolean;
var
  LDAP : TLDAPSend;
begin
  Result := False;
  if ( (Length(lpszUsername) = 0) or (Length(lpszPassword) = 0) )then Exit;
  LDAP := TLDAPSend.Create;
  try
    LDAP.TargetHost := lpszDomain;
    LDAP.TargetPort := '389';
    ....
    LDAP.UserName := lpszUsername + #64 + lpszDomain;;
    LDAP.Password := lpszPassword;
    Result := LDAP.Login;
  finally
    LDAP.Free;
  end;
end;

How can i check if the user set the correct password or not in a more reliable way using a similar technique?

Try to use FormatMessage function

 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM,
                nil,
                myResult,
                LANG_ENGLISH or SUBLANG_ENGLISH_US,
                lpMsg,
                0,
                nil);
 MessageBox(0, lpMsg, 'Msg', 0);
Kablesh answered 11/5, 2017 at 6:25 Comment(4)
Thanks, with LogonUser i was able to achieve my result (I update now my question to share the simple function i wrote). LogonUser does not raise exception as ADsOpenObject do. I tried my function on 3 different servers (with 3 different domain controllers) and it seems working.Jory
Morevoer the solution with LogonUser is simpler since adshlp, ActiveDs_TLB are not neededJory
You can use SysErrorMessage(myResult) instead of the FormatMessage(), which you aren't releasing the allocated buffer anyway. Honestly, I don't understand that part of the answer though. Probably I misread the question...Glycerinate
@SertacAkyuz, You did not misread the question. the FormatMessage is irrelevant here.Double
D
4

You need to check the error code that ADsOpenObject returns. do not base your error checking on the returned exception messages.

if the function succeeded it will return S_OK, otherwise you need to refer to ADSI Error Codes, specifically, the LDAP error codes for ADSI

When an LDAP server generates an error and passes the error to the client, the error is then translated into a string by the LDAP client.

This method is similar to Win32 error codes for ADSI. In this example, the client error code is the WIN32 error 0x80072020.

To Determine the LDAP error codes for ADSI

  1. Drop the 8007 from the WIN32 error code. In the example, the remaining hex value is 2020.
  2. Convert the remaining hex value to a decimal value. In the example, the remaining hex value 2020 converts to the decimal value 8224.
  3. Search in the WinError.h file for the definition of the decimal value. In the example, 8224L corresponds to the error ERROR_DS_OPERATIONS_ERROR.
  4. Replace the prefix ERROR_DS with LDAP_. In the example, the new definition is LDAP_OPERATIONS_ERROR.
  5. Search in the Winldap.h file for the value of the LDAP error definition. In the example, the value of LDAP_OPERATIONS_ERROR in the Winldap.h file is 0x01.

For a 0x8007052e result (0x052e = 1326) for example you will get ERROR_LOGON_FAILURE


From your comment:

Since the function always raises an exception i am not able to read the code

You are getting an EOleException because your ADsOpenObject function is defined with safecall calling convention. while other implementations might be using stdcall. when using safecall Delphi will raise an EOleException and the HResult will be reflected in the EOleException.ErrorCode, otherwise (stdcall) will not raise an exception and the HResult will be returned by the ADsOpenObject function.

Double answered 11/5, 2017 at 7:27 Comment(2)
Since the fucntion always raises an exception i am not able to read the code, anyway i managed to solve my issue as decribed in the question, thanks to the accepted answer. Thanks a lot for helpin!Jory
I changed the function instead of safecall to stdcall and it works fine: function ADsGetObject(lpszPathName:WideString; const riid:TGUID; out ppObject):HRESULT; stdcall;Pastore
K
2

i would like to know whether a user inputs the correct combination of Domain, User and Password for his Active Directory user.

You can use LogonUser function to validate user login e.g. :

  if (LogonUser(pChar(_Username), pChar(_ADServer), pChar(_Password), LOGON32_LOGON_INTERACTIVE,
   LOGON32_PROVIDER_DEFAULT, hToken)) then
  begin
    CloseHandle(hToken);
    //...
    //DoSomething
  end
  else raise Exception.Create(SysErrorMessage(GetLastError));

Note: You must use LogonUser within a domain machine to be able to use the domain login or the function will always return The user name or password is incorrect

The alternative is using TLDAPSend e.g. :

function _IsAuthenticated(const lpszUsername, lpszDomain, lpszPassword: string): Boolean;
var
  LDAP : TLDAPSend;
begin
  Result := False;
  if ( (Length(lpszUsername) = 0) or (Length(lpszPassword) = 0) )then Exit;
  LDAP := TLDAPSend.Create;
  try
    LDAP.TargetHost := lpszDomain;
    LDAP.TargetPort := '389';
    ....
    LDAP.UserName := lpszUsername + #64 + lpszDomain;;
    LDAP.Password := lpszPassword;
    Result := LDAP.Login;
  finally
    LDAP.Free;
  end;
end;

How can i check if the user set the correct password or not in a more reliable way using a similar technique?

Try to use FormatMessage function

 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM,
                nil,
                myResult,
                LANG_ENGLISH or SUBLANG_ENGLISH_US,
                lpMsg,
                0,
                nil);
 MessageBox(0, lpMsg, 'Msg', 0);
Kablesh answered 11/5, 2017 at 6:25 Comment(4)
Thanks, with LogonUser i was able to achieve my result (I update now my question to share the simple function i wrote). LogonUser does not raise exception as ADsOpenObject do. I tried my function on 3 different servers (with 3 different domain controllers) and it seems working.Jory
Morevoer the solution with LogonUser is simpler since adshlp, ActiveDs_TLB are not neededJory
You can use SysErrorMessage(myResult) instead of the FormatMessage(), which you aren't releasing the allocated buffer anyway. Honestly, I don't understand that part of the answer though. Probably I misread the question...Glycerinate
@SertacAkyuz, You did not misread the question. the FormatMessage is irrelevant here.Double
S
1

I get (HRESULT) 0x8007052e (2147943726) "unknown user name or bad password" when I use a wrong password. And there is no EOleException, try:

      hr := ADsOpenObject('LDAP://'+ ADomain + '/OU=Domain Controllers,' + APath,
                       AUser, APwd,
                       ADS_SECURE_AUTHENTICATION or ADS_READONLY_SERVER,
                       IID_IADs, pObject);
   if (hr=HRESULT(2147943726)) then ShowMessage ('wrong username or password')
Scolex answered 10/5, 2017 at 21:33 Comment(2)
Unfortunately i alwasy geT EOleException so i myResult is always 0, since the exception does not allow the ADsOpenObject to return a value to MyResult. How can i do? I started using E.Message because i can read it. please suggestJory
@user193655, I've updated the answer with what works and has been tested on DCs 2003 to 2016.Scolex

© 2022 - 2024 — McMap. All rights reserved.