I will debug the answer to this question in three different sections:
- Current issue with your methodology
- Problems with recommended solutions both on the Internet and in this topic
- Decision
Current issue with your methodology.
NetValidatePasswordPolicy requires that its InputArgs parameter accept a pointer to a structure, and the structure you pass in depends on your ValidationType . In this case, you pass NET_VALIDATE_PASSWORD_TYPE.NetValidateAuthentication , which requires InputArgs NET_VALIDATE_AUTHENTICATION_INPUT_ARG , but you pass a pointer to NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG .
In addition, you are trying to assign a type of currentPassword type to the structure NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG .
However, there is a big fundamental problem with using NetValidatePasswordPolicy , and you are trying to use this function to check passwords in Active Directory, but that’s not what it is used for. NetValidatePasswordPolicy used to allow applications to check for the authentication database provided by the application.
More information about NetValidatePasswordPolicy here .
Problems with recommended solutions both on the Internet and in this thread
Various articles on the Internet recommend using the LogonUser function found in AdvApi32.dll , but this implementation has its own set of problems:
Firstly, LogonUser checks the local cache, which means that you will not get immediate accurate account information unless you use the "Network" mode.
Secondly, the use of LogonUser in a web application, in my opinion, is a bit hacky, as it is intended for desktop applications running on client machines. However, given the limitations provided by Microsoft, if LogonUser gives the desired results, I don’t understand why it should not be used - a ban on caching problems.
Another problem with LogonUser is that how well it works for your use case depends on how your server is configured, for example: There are certain permissions that must be enabled in the domain that you authenticate against this need. to work in the "Network" mode.
Read more about LogonUser here .
In addition, GetLastError() should not be used; instead, GetLastWin32Error() should be used, since it is unsafe to use GetLastError() .
Read more about GetLastWin32Error() here .
Decision.
To get the exact error code from Active Directory, without any caching problems and directly from directory services, this is what you need to do: rely on COMException returning from AD when an account problem occurs, because ultimately, mistakes are what you are looking for.
First, here you raise an error from Active Directory when authenticating the current username and password:
public LdapBindAuthenticationErrors AuthenticateUser(string domain, string username, string password, string ouString) { // The path (ouString) should not include the user in the directory, otherwise this will always return true DirectoryEntry entry = new DirectoryEntry(ouString, username, password); try { // Bind to the native object, this forces authentication. var obj = entry.NativeObject; var search = new DirectorySearcher(entry) { Filter = string.Format("({0}={1})", ActiveDirectoryStringConstants.SamAccountName, username) }; search.PropertiesToLoad.Add("cn"); SearchResult result = search.FindOne(); if (result != null) { return LdapBindAuthenticationErrors.OK; } } catch (DirectoryServicesCOMException c) { LdapBindAuthenticationErrors ldapBindAuthenticationError = -1; // These LDAP bind error codes are found in the "data" piece (string) of the extended error message we are evaluating, so we use regex to pull that string if (Regex.Match(c.ExtendedErrorMessage, @" data (?<ldapBindAuthenticationError>[a-f0-9]+),").Success) { string errorHexadecimal = match.Groups["ldapBindAuthenticationError"].Value; ldapBindAuthenticationError = (LdapBindAuthenticationErrors)Convert.ToInt32(errorHexadecimal , 16); return ldapBindAuthenticationError; } catch (Exception e) { throw; } } return LdapBindAuthenticationErrors.ERROR_LOGON_FAILURE; }
And these are your "LdapBindAuthenticationErrors", you can find more on MSDN here .
internal enum LdapBindAuthenticationErrors { OK = 0 ERROR_INVALID_PASSWORD = 0x56, ERROR_PASSWORD_RESTRICTION = 0x52D, ERROR_LOGON_FAILURE = 0x52e, ERROR_ACCOUNT_RESTRICTION = 0x52f, ERROR_INVALID_LOGON_HOURS = 0x530, ERROR_PASSWORD_EXPIRED = 0x532, ERROR_ACCOUNT_DISABLED = 0x533, ERROR_ACCOUNT_EXPIRED = 0x701, ERROR_PASSWORD_MUST_CHANGE = 0x773, ERROR_ACCOUNT_LOCKED_OUT = 0x775 }
Then you can use the return type of this Enum and do what you need with it in your controller. It is important to note that you are looking for a piece of the “data” line in the “Extended error message” of your COMException , because it contains the pop-up error code you are looking for.
Good luck and I hope this helps. I tested it and it works great for me.