Custom Multi-factor Active Directory Authentication - c#

I'll start off by saying that I have no idea if what I want can actually be done. If that's the case, do not hesitate to tell me that I'm dreaming.
I want to create a custom active directory "authenticator" in C#. By that, I mean, I would like that whenever someone logs in, their password stored in the AD is checked first, and then a second step of authentication is performed. Only if both steps pass does the user get to log in.
Now, I imagine the above isn't too far fetched, providing I wanted to integrate this authenticator into a custom product, right?. Am I totally insane for also wondering if this authenticator can be used when, say, logging into Windows itself? Or perhaps a pre-existing product which authenticates against the AD?
If I'm not dreaming, would anyone also know of any good articles/APIs to get me going? The APIs don't have to be free, as I'm willing to part with some cash to get things moving faster.

This is entirely feasible. However I'd like to note that, when issuing a server bind to Active Directory, you're checking the provided username (usually the sAMAccountName) and the password entered in one action. There are a few ways of doing this in C#, but many folks (including myself) have opted to use the System.DirectoryServices and System.DirectoryServices.Protocols namespace.
This is how I currently bind users to Active Directory, which then based on the result of this method, I either display the reason for authorization failure, or I allow them to continue on to their account within the application.
//Define your connection
LdapConnection ldapConnection = new LdapConnection("123.456.789.10:389");
try
{
//Authenticate the username and password
using (ldapConnection)
{
//Pass in the network creds, and the domain.
var networkCredential = new NetworkCredential(Username, Password, Domain);
//Since we're using unsecured port 389, set to false. If using port 636 over SSL, set this to true.
ldapConnection.SessionOptions.SecureSocketLayer = false;
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
//To force NTLM\Kerberos use AuthType.Negotiate, for non-TLS and unsecured, use AuthType.Basic
ldapConnection.AuthType = AuthType.Basic;
ldapConnection.Bind(networkCredential);
}
catch (LdapException ldapException)
{
//Authentication failed, exception will dictate why
}
}
If you'd like to go a step further and retrieve properties about this user as well, check out this thread here.
Also, I highly recommend Softerra's LDAP Browser for testing anything LDAP related - it is a wonderful product, and it's free. You can download it from here.
Hopefully that gets you going in the right direction.

Related

c# - Validating expired domain credentials across the forest

There are already a few questions which ask how to validate Active Directory domain questions. However, I do not believe they deal adequately with multi-domain scenarios across the forest. For starters, they both suggest that the most definitive way to perform AD authentication in C# with .NET 3.5+ should look something like this:
bool isValid = false;
using(var ctx = new PrincipalContext(ContextType.Domain, "foo.com"))
{
// verify user exists first
var lookedUp = UserPrincipal.FindByIdentity(ctx, "myuser");
if (lookedUp != null)
{
// validate credentials
isValid = pc.ValidateCredentials("myuser", "mypassword");
}
}
This is all well and good when the user you want to authenticate belongs to foo.com. However, there are some subtle unexpected differences in the case of a user belonging to a child domain, whose password is also expired. (I have successfully got the PrincipalContext to find the user in the child domain two different ways: (a) setting the domain argument to "foo.com:3268"; (b) adding a container argument w/ the base DN of the child domain: new PrincipalContext(ContextType.Domain, "foo.com", "dc=child,dc=foo,dc=com"). The problem I describe below occurs in both scenarios.)
If the user belongs to foo.com, and has an expired password, ValidateCredentials returns true (at least on Windows 7 where I'm testing; I've seen others say the behavior is different in Windows 2003). However, if the user belongs to child.foo.com and the password is expired, then ValidateCredentials returns false.
It is pretty important for me to be able to distinguish between "valid password but expired" and "invalid password". If the entered password is correct but expired, then I want to be able to redirect them to a "change password" screen. However, if the password they entered was totally wrong, then it could be considered leakage of information to forward them to a change password screen.
The only way forward I see is to use the LDAP interface to AD and try to parse the status messages it sends back to figure out the underlying error code. This sounds neither fun nor wise.
Can anyone provide a reason for this behavior when dealing with subdomains within the same forest? Can anyone provide a solution to what I am trying to accomplish here?
So the issue here it appears is that .NET ultimately tries to do what's called a fast concurrent LDAP bind to AD. That's a super lightweight mechanism and Google seems to indicate that perhaps it bypasses the expiry check. I didn't validate this, but, assuming it's true...
I think your options here are to either a) do the binding yourself (look at the LDAPConnection class and the associated flags) or b) P/Invoke LogonUser. You may need to dig in to figure out the passwory expiry status if the call fails as I'm not sure if either of those will tell you that it's expired or isn't as the reason for the failure.

active directory authentication with status/error code result

Currently we (myself and my company) have an asp.net mvc4 page. We wish to utilize a logon page which authenticates via AD. One requirement being with an unsuccessful attempt we give back some information to the user.
The information we would like to have would be something like:
Invalid user/pw
Account is locked
Password expired
This is unfamiliar territory so I'm not sure what .NET libraries may be available. So far I've only come across the System.DirectoryServices but it doesn't seem I will get results beyond a bool.
Is this possible? Any references, suggestions, or examples would be greatly appreciated!
You can use PrincipalContext.ValidateCredentials to validate your credentials first. If false is returned, use the static UserPrincipal.FindByIdentity to find your user then, if found, look to see if the account is locked out using IsAccountLockedOut().
You might need to extend UserPrincipal yourself to see if the password is expired, I'm not seeing a direct property/method. You can extend it to access the userAccountControl attribute directly and check to see if bit 0x800000 is set, which is PasswordExpired. Here is more information on the userAccountControl values.
Suppose you have code like this
try
{
SearchResult result = searcher.FindOne();
}
catch(Exception e)
{
// now what?
}
Now in Exception you can deal with LDAP exception type, Here is the List of all LDAP error's.
http://msdn.microsoft.com/en-us/library/aa746530(v=vs.85).aspx
You can identify on the basis of ADSI Error Value which type of error you are getting.
But according to me you should give user a single common error like invalid credentials because LDAP error are much hard to deal with.
Cheers.!!

Hide password in c# source code

I am using the following code in c# to download a file from my website:
WebClient webClient = new WebClient();
webClient.Credentials = new System.Net.NetworkCredential("username", "password");
webClient.DownloadFile("http://example.com/file.txt", "file.txt");
The file is only downloaded when certain criteria are met, so I don't want the users to be able to access the files on my site
My corcern is that if a curios users decompiles the code, he will find the password and be able to access all the files on my site.
I've read that a secure way to save the password is to store its hash, but I don't know how to implement it in this example.
What options do I have to keep my password secure and make it impossible for a user to find it?
A sobering reality: You can't protect information contained in your program like this.
A must-do: Choose a username/password that is only for accessing the special files this single program needs - not your "access my whole website" username and password.
But just know that all you are doing is adding a little bit of an obstacle, here; anyone who wants to can examine your program and find the username and password.
The only 'correct' way to do this is to do it based on the user's own credentials; their username and password within your own system, for example. Then you would need to give them access based on that information, and your program would need to prompt them for it.
You simply don't. Users give you passwords to do stuff, not the other way around.
If the user has to prove "certain conditions", then pass proof of those certain conditions to the server, and let it decide whether to allow the download or not.
There is no way to prevent that. If you program is able to access the file under condition X, the user is able to trick the program into condition X and get the file no matter what. You can make it harder, but you can't make it impossible.
If the data are in the program itself you can considered them as already being exposed to users. If the credentials are on the users computer regardless on how many measures you take to combat this there is always a possibility to find a way around it.
What you can do is implement a login form for your program and provide the users with login info. Then when the user enters the login info do a check on the server side if the credentials exist (usually by checking in a database) and if it matches send them the file.
But as always, there is the issue with users just sharing the login info with other people and so on.

Get users NT Credentials in Winform App

I have a winform app that requires me to check whether a page exists on our Wiki or not. However, that query requires a users NetWork credentials to be passed.
I don't want to hardcode my credentials, and I don't want users to have to manipulate the App.config everytime someone else uses it (there's also the issue of them having to expose their password in the App.Config). Is there a way I can get the current user's NT credentials? I don't need to see it (that would be an obvious security issue), I just need it for the following line of code:
HttpWebRequest WikiPageRequest;
WikiPageRequest.Credentials = new NetworkCredential("user", "pass", "dom");
//maybe do something like this
WikiPageRequest.Credentials = GetNTCredentials();
Can you try using CredentialCache.DefaultCredential? That seems to get you the current logged in users credential.
If by "wiki", you mean a deployment of MediaWiki, then sadly you need user id and password.
Unfortunately you cannot get it from Windows, because, even the Windows OS itself does not know the password. It just stores a hash of the password.
By the way, why dont you ask for user id/password for wiki each time the user starts the app?

How can I convince Internet Explorer to allow authentication as another user?

Thanks for reading and for your thoughts; this is a hairy problem, so I thought I'd share to see if it is actually a fair challenge for more seasoned developers than ourselves.
We're developing a web application for a corporate Microsoft Active Directory environment, and we use Windows Authentication provided by IIS to authenticate users for single-sign-on, alongside Forms Authentication. I know IIS complains when both are enabled, but it works very well, and every site we've deployed at has had no weird quirks to work around - until now.
The new site has "shared" machines, logged in permanently with a generic account that has read-only access to the applications they need to use. This means that we can't differentiate between users who should have different permissions to the application; we need some way of prompting the user for authentication details.
First try was some serious googling; nobody else in the world seemed to have our problem except for a few misguided souls who had asked questions into the ether and received no response.
After a bit of brainstorming and nutting out the way IIS's authentication works, it seemed that the most straightforward way to approach the problem was to issue a 401 Unauthorized in response to a user known to be a shared account. Initial tests here seemed fruitful, yielding successful changes of username inside the browser, however a prototype at the site did not prompt for credentials, and the browser kept the same account details. We also hit on the IE-specific javascript
document.execCommand("ClearAuthenticationCache")
which, again, worked in the lab but not onsite. Further experiments with IE security settings onsite revealed that the browser would automatically reauthenticate if the webapp site was excluded from the Intranet Zone, regardless of the method used to trick the browser into prompting the user for new account details.
Now we're stuck. We've got workaround options for getting it going on time, but they're definitely not the "right" answers:
require users to log out of the shared account before logging into our app (...yuck)
exclude our webapp from Intranet Zone on all machines
provide a non-SSO login service for users
I'm convinced that there's a canonical way to do this - a known pattern, a common base problem that's already been solved, something like that - and I'm very interested to hear what sort of inventive methods there are to solve this sort of problem, and if anyone else has actually ever experienced anything remotely like it.
We ended up settling on a solution that submits a query to the LDAP directory the server knows about. It means having to accept the user's password, but no other solution was solid enough to run in a production environment.
Hopefully this helps someone. .NET Framework 3.5+ required.
using System.DirectoryServices.AccountManagement;
private static bool IsLdapAuthenticated(string username, string password)
{
PrincipalContext context;
UserPrincipal principal;
try
{
context = new PrincipalContext(ContextType.Domain);
principal = Principal.FindByIdentity(context, IdentityType.SamAccountName, username) as UserPrincipal;
}
catch (Exception ex)
{
// handle server failure / user not found / etc
}
return context.ValidateCredentials(principal.UserPrincipalName, password);
}
Could you not create a page to which the shared accounts are denied access. Then do a redirect to that page, with a return URL encoded in the query string, at any point where you need the user to reauthenticate with a non-shared account? This should trigger the browser to put up the usual login dialog.
After the user reauthenticates, the new page should just redirect back to the return URL in the query string.

Categories