Validating a user's credentials remotely - c#

I currently use LogonUser() to authenticate my user's username and password on my local domain at the office and it works great for what i need it to do.
Since I developed the app I now need to make it work over my VPN. It seems LogonUser() will not work with REMOTELY validating credentials. Or will it? Is it possible to use LogonUser() to validate a user's credentials on a REMOTE domain account?
I have read in some places that using LOGON32_LOGON_NEW_CREDENTIALS for the 4th param (login type) and LOGON32_PROVIDER_WINNT50 for the 5th param (provider) would do the trick. But every time I try that I ALWAYS get success... I can supply a bogas user and pass and it will work every time :(.
Ideas?
Edit - Added Notes
Tried to use this function but I kept getting the exception telling me the user/pass was bad.
public bool Win2kCredentialsIsValid(string domain, string username, string password)
{
string adPath = "LDAP://" + domain + "/rootDSE";
DirectoryEntry adRoot = new DirectoryEntry(adPath, domain + "\\" + username, password, AuthenticationTypes.ReadonlyServer);
try
{
object o = adRoot.Properties["defaultNamingContext"];
}
catch
{
return false;
}
return true;
}
--
Edit - Added More Notes
OK so I tried yet another example just to get it to work and started down this path, and there are a few things to note...
MyServerHostName is exactly that, my server's hostname. EX: 'Server01'.
My domain name in this example is 'MyDomain.local'
So that makes my FQN for the server 'Server01.MyDomain.local'
I tried to make this work and got the following error...
The supplied context type does not match the server contacted. The server type is Domain.
This errored out at : var context = new PrincipalContext(ContextType.ApplicationDirectory, "MyServerHostName:389", "DC=MyDomain,DC=local"))
private bool CheckADCredentials()
{
bool bResults;
using (var context = new PrincipalContext(ContextType.ApplicationDirectory,
"MyServerHostName:389",
"DC=MyDomain,DC=local"))
{
var username = "firstname.lastname";
var email = "firstname.lastname#MyServerHostName";
var password = "123456";
var user = new UserPrincipal(context)
{
Name = username,
EmailAddress = email
};
user.SetPassword(password);
user.Save();
if (context.ValidateCredentials(username, password, ContextOptions.SimpleBind))
{
bResults = true;
}
else
{
bResults = false;
}
user.Dispose();
}
return bResults;
}

I ended up going with a different solution. Instead of trying to validate a user's account on a domain that my PC was not connected to I ended up caching my domain credentials in the database and just built a salted MD5 type encrypt function so it would make it hard .. er.. for someone to crack it. ;)
Now I just validate against cached credentials in the database when working remotely... It just required the user to first login on the domain but then the user can use it remotely day and night. ;)
Thanks!

Related

Pass String Variable Into Nework Credential Constructor for Password Arguement

What I have is a function that allows a domain user to be authenticated against their LDAP credentials. However, it works long as I hard-code a known password as a raw string... which is a no-no, of course. I wish to pass in a string value received from a TextBox I have set up. Here is the function:
public static bool fnValLDAPCreds()
{
bool validation;
try
{
LdapConnection ADConn = new LdapConnection(new LdapDirectoryIdentifier((string)null, false, false));
NetworkCredential NetCred = new NetworkCredential(Environment.UserName, "Password123", Environment.UserDomainName);
ADConn.Credential = NetCred;
ADConn.AuthType = AuthType.Negotiate;
// the user's authenticated here; creds used to login on the domain controller.
ADConn.Bind(NetCred);
validation = true;
MessageBox.Show("You were successfully authenticated against AD using LDAP!");
}
catch (LdapException)
{
validation = false;
MessageBox.Show("Your login was unsuccesful. Try a different set of credentials.");
}
return validation;
}
What I've tried to do was substitute in a value from my TextBox, but since it lies in the static bool I have not been successful with making any external references to a control in the current context. I'm calling this function in button handler to fire it off. How can I swap in a string DomPassWord variable that gets its value from the textbox I have setup to obtain it?
NetworkCredential NetCred = new NetworkCredential(Environment.UserName, DomPassWord, Environment.UserDomainName); is what I'm striving for, as I can securely match a password in the domain with no hard-coding, using something like DomPassWord = txtUserPW.Text. Tried the SecureString route, but was unsuccessful in that regard as well. Any ideas?
You cannot access text boxes inside a static method, since they aren't static fields (at least it looks like it from what you've written).
But you can simply pass your arguments to your method. Change it to something like this:
public void ButtonClick(object sender, EventArgs args)
{
// bool valid = fnValLDAPCreds(Environment.UserName, "Password123", Environment.UserDomainName);
bool valid = fnValLDAPCreds(txtUserName.Text, txtUserPW.Text, Environment.UserDomainName);
}
public static bool fnValLDAPCreds(string username, string password, string domain)
{
try
{
LdapConnection ADConn = new LdapConnection(new LdapDirectoryIdentifier((string)null, false, false));
NetworkCredential NetCred = new NetworkCredential(username, password, domain);
ADConn.Credential = NetCred;
ADConn.AuthType = AuthType.Negotiate;
// the user's authenticated here; creds used to login on the domain controller.
ADConn.Bind(NetCred);
MessageBox.Show("You were successfully authenticated against AD using LDAP!");
return true;
}
catch (LdapException)
{
MessageBox.Show("Your login was unsuccesful. Try a different set of credentials.");
return false;
}
}
Kinda tangential, but have you thought about AuthType.Ntlm? If all you're doing this for is to make sure user 'Charlie' is actually Charlie by making him type in his password? Then you're on the right track. But if you're trying to connect in to AD using the current user credentials as a way of getting to AD itself? Then you might want to take a look at
ADConn.AuthType = AuthType.Ntlm;
... and letting windows handle this for you (no need to have the user type in a password at all - it'll use their current windows credentials.)

Not getting Active Directory details after hosting

I wrote C# code to get email from Active Directory. It is working fine on my local system, but after hosting I am not getting email address. Followings are the things I already tried -
Changed application pool identity to NetworkService
Enabled Windows and Digest Authentications (both at the same time and one by one too)
Code:
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "comppany.com" , "DC=compnay,DC=com", ContextOptions.Negotiate))
// tried above and below//(ContextType.Domain, System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName))
{
// validate the credentials
bool isValid = pc.ValidateCredentials(Uid, Pwd);
if (isValid)
{
try
{
using (UserPrincipal up = UserPrincipal.FindByIdentity(pc, Uid))
{
return up != null && !String.IsNullOrEmpty(up.EmailAddress) ? up.EmailAddress : string.Empty;
}
//return "Validate successfully.";
}
catch (Exception ex)
{
return ex.Message;
}
}
}
Also tried following -
using (var connection = new DirectoryEntry())
{
using (var search = new DirectorySearcher(connection)
{
Filter = "(samaccountname=" + Uid + ")",
PropertiesToLoad = { "mail" },
})
{
return (string)search.FindOne().Properties["mail"][0];
}
}
None of them are working after hosting the app in IIS7.0
Please help.
Thanks
It will be because your user (i.e. you) will have rights to read from Active Directory, but the IIS user and Network Service won't.
Put a try catch round the using statement thing and you should see this in the exception.
There are alternative PrincipalContext constructors that allow you to specify the details of the user to connect as, or you could change the IIS app pool to run as a user with rights - I'd go with the PrincipalContext way though.
As a quick test try this version of the PrincipalContext constructor - put your username and password in the username and password paramemetrs and see if it works when hosted in IIS - if this works then you need to come up with some way of passing the user details in via config. (Generally a service account with only the rights to read, whose password does not change often is used for this)

Why does open OpenLDAP require a cn=username?

I'm connecting to OpenLDAP with C#, and when I pass in my username and password, I have to pass them into my LdapConnection object as cn=Username, Password. If I just pass in username and password my call to Bind fails. Why do I have to do that? Is something misconfigured on my OpenLDAP server?
It's just a byproduct of the implementation. Novell's eDirectory solution takes a very similar approach, and I use the same Novell.Directory.Ldap code to handle bind requests to both eDirectory and OpenLDAP. Now obviously, the users themselves shouldn't have to enter their entire CN when authorizing - we can just issue a search for them, based of thier UID :
//Setup the initial bind for the admin user
var lc = new LdapConnection();
lc.SecureSocketLayer = SSL;
lc.UserDefinedServerCertValidationDelegate += delegate { return true; };
lc.Connect(ServerName, Port);
lc.Constraints.TimeLimit = Timeout;
lc.Bind(AdminUsername, AdminPassword);
Now I just filter for the user, and bind using their distinguished name, or full container name (CN) :
//Ex. (uid=jsmith)
string filter = config.LdapAuth.LdapFilter.Replace("{{uid}}", username);
//Find the user we're trying to authorize
var lsc = lc.Search(config.LdapAuth.LdapDomain, LdapConnection.SCOPE_SUB, filter, null, false);
if (lsc.hasMore())
{
LdapEntry nextEntry = lsc.next();
//Check the Entries DN so we can properly bind
lc.Bind(nextEntry.DN, Password);
}
This was the most widely used approach I could find, and it's worked quite well so far.

Active Directory PrincipalContext.ValidateCredentials domain disambiguation

I'm dealing with two domains - one is a trusted domain. There may be a JohnSmith on one domain and another JohnSmith on the other. Both of these people need to log into my application.
My problem: it doesn't matter which domain I pass in - this code returns true! How do I know which JohnSmith is logging in?
static public bool CheckCredentials(
string userName, string password, string domain)
{
using (var context = new PrincipalContext(ContextType.Domain, domain))
{
return context.ValidateCredentials(userName, password);
}
}
The ValidateCredentials works with userPrincipalName you perhaps can try to build the first parameter (username) combining the login and the domain to create the username JohnSmith#dom1.com versus JohnSmith#dom2.com.
You can always retrieve the full DN of the user who has logged in using
UserPrincipal up = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, userName);
up.UserPrincipalName // shows user#domain.com
up.DistinguishedName // shows CN=Surname,OU=group,DC=domain,DC=com
up.SamAccountName // shows login name
Use the up.SamAccountName to subsequent calls to ValidateCredentials including the domain name - you can't have 2 users who log in using the same sAMAccountName after all!
The DistinguishedName will definitely show you which JohnSmith logged in.
Based on JPBlanc's answer, I've re-written my code. I've also added a try/catch in case a bogus domain is passed in.
static public bool CheckCredentials(
string userName, string password, string domain)
{
string userPrincipalName = userName + "#" + domain + ".com";
try
{
using (var context = new PrincipalContext(ContextType.Domain, domain))
{
return context.ValidateCredentials(userPrincipalName, password);
}
}
catch // a bogus domain causes an LDAP error
{
return false;
}
}
The accepted answer will fail with Domains that contain different email addresses within them. Example:
Domain = Company
User1 = employee#department1.com (under company Domain)
User2 = employee2#Department2.com (under company Domain)
The provided answer will return false using:
userName = "employee";
domain = "company";
string userPrincipalName = userName + "#" + domain + ".com";
The correct way to encompass users across domains is:
string userPrincipalName = userName + "#" + domain;
without the .com portion it searches the user AT that domain instead of searching for an email within a global domain.

Get UPN or email for logged in user in a .NET web application

I'm not a .NET developer, and I have a feeling this would be trivial for someone who is:
I have a C# web application that makes user of the user credentials of the logged in user. Currently it uses the SID which comes from
System.Security.Principal.WindowsIdentity.GetCurrent().User.Value
I need to get either the users UPN login or email address (as defined in active directory) instead of the SID. GetCurrent() returns an object of type WindowsIdentity; looking in the details for WindowsIdentity Members:
MSDN: WindowsIdentity Members
I can't see anything that looks like it would give me either the UPN or email in there. How can I pull up that information to use, either by feeding the SID into some other function or calling something different in the first place.
Meanwhile (.NET 3.5) this is a one-liner:
System.DirectoryServices.AccountManagement.UserPrincipal.Current.EmailAddress
for the email, or
System.DirectoryServices.AccountManagement.UserPrincipal.Current.UserPrincipalName
for the UPN.
To query active directory using a directory searcher you need to do something like this (totally untested code):
string userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
string ldapPath = "LDAP://domain.company.com";
public string GetEmail(string userName, string ldapPath)
{
using (DirectoryEntry root = new DirectoryEntry(ldapPath))
{
DirectorySearcher searcher = new DirectorySearcher(root);
searcher.Filter = string.Format(#"(&(sAMAccountName={0}))", userName);
searcher.PropertiesToLoad = "mail";
SearchResult result = searcher.FindOne();
if (result != null)
{
PropertyValueCollection property = result.Properties["mail"];
return (string)property.Value;
}
else
{
// something bad happened
}
}
}
Try:
System.Security.Principal.WindowsIdentity.GetCurrent().Name

Categories

Resources