This question might match with question at link here indirectly.
I am working on website project based on Asp.Net 4.0 for corporate use.
There is a form in website which ask users for their AD username and password with domain name selected by default.
I know of ways to authenticate user by root domain name. But there are users whose domain names(UPN suffix) had been modified.
For e.g. the domain name is xyz.com. So user is authenticate by user#xyz.com and their passwords. But for some users their name is user#abc.com.
So how to validate such users with alternative UPN suffix other than root domain name?
After lot of search with hit and trial method, I was able to formulate solution for it with reason.
The following link User Principal Name in AD by Jorge de Almeida Pinto is worth mentioning here. Please get details for iUPN and eUPN from there.
I am explaining my problem statement again as scenario to make it more clear.
Scenario
There are only two users in AD in domain (domain.com) named as Anil and Alex.
iUPN for Anil is Anil#domain.com and that of Alex is Alex#domain.com (which is by default set by AD itself).
eUPN for Anil is been left blank (which means it will be Anil#domain.com, the default behavior of AD). But for Alex it is been set as Alex#dummy.com for any reason.
You can get idea for AD interaction from link Active Directory With C# which I found nicely written.
As a programmer, I want to write code for making these both users get logged in AD from code.
Issues
Anil#domain.com get logged in successfully.
Alex#dummy.com cannot get logged in.
Reason
I had not been able to found perfect root cause for it.
But my guess is that, AD itself put domain name after # (at the rate). Since domain name for Alex is dummy.com, so AD tries to found user with suffix as #dummy.com. And return result as no user found.
Solution
The solution was to dissect username and domain name.
Append root domain name (domain.com) as suffix to user (with separate domain name). And then try to login.
You can have questions that other unauthorized user can also get in by this way. No! Because passwords need to be matched.
Why it worked?
Because AD was able to found user with Alex#domain.com in domain.com.
Edit
The solution I provided work only for case when other user is having same sAMAccountName with same domain name.
But what if the sAMAccountName is itself set with as Alex#dummy.com. So true solution was to go as -
(1) Get sAMAccountName on basis of UPN.
/// <summary>
/// Get sAMAccountName for matching UserPrincipalName (UPN)
/// </summary>
/// <param name="domain">Domain name</param>
/// <param name="userName">Username</param>
/// <returns></returns>
protected string GetSamUsername(string domain, string userName)
{
string samName;
using (var pc = new PrincipalContext(ContextType.Domain, domain))
{
var user = UserPrincipal.FindByIdentity(pc, userName); // Search for this user
if (user == null) return null; // If user is not there, why go forward
samName = user.SamAccountName;
}
return samName;
}
(2) Now logging in by any user will work.
It also helps us to authenticate user existence in AD.
If your first attempt fails (using default domain name), display the form with the domain name option.
Or, provide a textbox for the domain name that is filled out ahead of time that your users can modify if necessary.
When authentication fails, be sure to show them a message indicating they need to pay attention to the domain name you have shown.
UPDATE:
private void AuthenticateUser(string loginID, string pwd) {
var search = new DirectorySearcher(m_rootDir);
if (-1 < loginID.IndexOf("#")) {
search.Filter = "(&(objectClass=user)(SAMAccountName=" + loginID + "))";
} else { // this is their Common Name
search.Filter = "(&(objectClass=user)(cn=" + loginID + "))"; // Get User By Full Name
}
// more code here
}
Related
I will start by describing how my application works today without LDAP.
I have WPF application that consumes WCF services (authentication windows or UserName depends on users choice). This services allows communication with database.
I display to user a "Login screen" in order to allow him set her "user name" and "password" then application connects to service and consumes function that checks if UserName and Password exist in database. (see img below)
Now I need also to integrate LDAP for authenticating user accounts against their existing systems instead of having to create another login account.
I'm bit ignorant about LDAP and confused about many things. Please excuse the possible use of wrong terminology.
I googled but I still don't have answers of many questions.
1- What is the relation between users that exist in my database table "User" and profiles that I should be created in LDAP ?
2- What is the check I should do to allow user come from LDAP to access to my application and use all functionnalities of my service ?
3- Should I have service type "LDAP" like other authentications types I have today in my application ("Windows" and "UserName") ?
4- If I want to update my application architecture described in picture above where should I add LDAP ?
First I am going to answer your questions one by one,
The user on LDAP is the same on DB, you can hold LDAP's Username and it's domain in your Users Table,
but the profile on the LDAP may vary with your profile table, but it can be fetched from LDAP address.
It's enough to check username and password over LDAP, just need to hold LDAP addresses in a Table (example ExternalPath) and make a relation between User and ExternalPath tables. LDAP address is contains some specifications.
Yes, you have to have a separate mechanism for identifying LDAP Users which I will explain more further.
This is not hard if everything be atomic and designed right, in further steps you may see it is easy.
So let me tell about my experience in LDAP and Authenticate users on LDAP and DB and our architecture.
I was implemented a WCF service named Auth.svc, this service contains a method named AuthenticateAndAuthorizeUser this is transparent for user which came from LDAP or anywhere.
I hope you get the clue and architecture to Authenticate user over LDAP and DB in below steps:
1- First I have a table named Users which hold users info and one more field named ExternalPath as foreign key, if it is null specify UserName is in DB wit it's password otherwise it is came from UserDirectory.
2- In second step you have to hold LDAP address (in my case LDAP addresses are in ExternalPath table), all LDAP addresses are on port 389 commonly.
3- Implementing authenticate User, if is not found(with Username and Password) then check it's ExternalPath to verify over LDAP address.
4- The DB schema should be something like below screenshot.
As you can see ExternalPath field specify user is from LDAP or not.
5- In presentation layer I am defining LDAP servers like below screenshot also
6- In the other side while adding new user in system you can define LDAP for user in my case I am listing LDAP titles in a DropDown in adding User form (if admin select LDAP address then don't need to get password and save it in DB), as I mentioned just need to hold LDAP username not password.
7- But last thing is authenticating user on LDAP and DB.
So the authenticate method is something like:
User userLogin = User.Login<User>(username, password, ConnectionString, LogFile);
if (userLogin != null)
return InitiateToken(userLogin, sourceApp, sourceAddress, userIpAddress);
else//Check it's LDAP path
{
User user = new User(ConnectionString, LogFile).GetUser(username);
if (user != null && user.ExternalPath != null)
{
LDAPSpecification spec = new LDAPSpecification
{
UserName = username,
Password = password,
Path = user.ExternalPath.Path,
Domain = user.ExternalPath.Domain
};
bool isAthenticatedOnLDAP = LDAPAuthenticateUser(spec);
}
}
If userLogin does not exist in DB by entered UserName and Password then we should authenticate it over related LDAP address.
In else block find User from Users table and get it's ExternalPath if this field was not null means User is on LDAP.
8- The LDAPAuthenticateUser method is :
public bool LDAPAuthenticateUser(LDAPSpecification spec)
{
string pathDomain = string.Format("LDAP://{0}", spec.Path);
if (!string.IsNullOrEmpty(spec.Domain))
pathDomain += string.Format("/{0}", spec.Domain);
DirectoryEntry entry = new DirectoryEntry(pathDomain, spec.UserName, spec.Password, AuthenticationTypes.Secure);
try
{
//Bind to the native AdsObject to force authentication.
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + spec.UserName + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (null == result)
{
return false;
}
}
catch (Exception ex)
{
Logging.Log(LoggingMode.Error, "Error authenticating user on LDAP , PATH:{0} , UserName:{1}, EXP:{2}", pathDomain, spec.UserName, ex.ToString());
return false;
}
return true;
}
If exception raised in LDAPAuthenticateUser means User does not exist in User Directory.
The authentication code accepts a domain, a user name, a password, and a path to the tree in the Active Directory.
The above code uses the LDAP directory provider the authenticate method calls LDAPAuthenticateUser and passes in the credentials that are collected from the user. Then, a DirectoryEntry object is created with the path to the directory tree, the user name, and the password. The DirectoryEntry object tries to force the AdsObject binding by obtaining the NativeObject property. If this succeeds, the CN attribute for the user is obtained by creating a DirectorySearcher object and by filtering on the SAMAccountName. After the user is authenticated and exception not happened method returns true means user find on given LDAP address.
To see more info about Lightweight Directory Access Protocol and authenticate over it THIS Link can be useful which tells about specification more.
Hope will help you.
The company that I work for has two active directories (ad1.com, ad2.com) because it has two different stores that had nothing to do with each other and plenty of users but now the managers of both stores need a page in common.
So I need to create a login where the users could access using just their Active Directory username and password.
In the login page there should be a list to choose the active directory and after that the user Paul(paul#ad1.com) and the user Paul(paul#ad2.com) with their respective password should be able to access to the page.
I have the code needed for the logIn page for one AD and works great but I don't know if it's possible to make the page available for two ADs.
Do I need some extra configuration on the server?
I google this but didn't find anything related.
If you have the code required for one AD, you could very well use the same code with slight modifications to authenticate against another AD.
Based on the domain name, you can identify the settings for AD and perform your authentication.
In cases where you use two AD, only identifying the domain name matters E.g.
ad1\username or username#ad1.com. You will not be able to achieve a login without the user specifying the domain name.
Have you tried using LDAP Authentication and just definining it differently based on the dropdown list?
Something like:
string adPath = string.Empty;
string domainName = string.Empty;
switch (ddlOption.ToString())
{
case "ad1":
adPath = "ad1Path";
domainName = "ad1";
break;
case "ad2":
adPath = "ad2Path";
domainName = "ad2";
break;
}
LdapAuthentication adAuth = new LdapAuthentication(adPath);
if (adAuth.IsAuthenticated(domainName, username, password))
{
//redirect Logic
}
I'm working on a project where the client wants to restrict some content to only Active Directory users . Is there any way to identify that a SPUser is an AD user short of parsing the username string for the domain (or something along those lines). Something like SPUser.IsADUser would be awesome.
Edit
This seems to work, but I'm not sure if this is reliable enough? For this use case, identifying that a user is a windows user is enough (there are no local system accounts)
SPUser user = SPContext.Current.Web.CurrentUser;
string userName = user.LoginName.Substring(user.LoginName.IndexOf('|') + 1);
SPPrincipalInfo info = SPUtility.ResolveWindowsPrincipal(SPContext.Current.Site.WebApplication, userName, SPPrincipalType.User, false);
if(info != null){
//THIS IS A WINDOWS ACCOUNT
}
In my experience it is much better to use audiences for this purpose. You then can easily trim any web part using "Audience" property. You can read about audiences here. Of course it will only work if you have user profile synchronization configured.
I am writing a small app that integrates with AD and will be listing users and allowing members of the site to edit certain fields of the users. These fields will be, first name, last name, display name and email.
When I run the following code I get an exception that says
The server is unwilling to process the request.
Please note that result is not null and contains the correct active directory user that I want to edit. Also note that result is of type SearchResult. Also note that the user I am using to connect to AD is the Administrative user.
DirectoryEntry entryToUpdate = result.GetDirectoryEntry();
entryToUpdate.Properties["cn"].Value = user.Name;
entryToUpdate.Properties["mail"].Value = user.Email;
entryToUpdate.Properties["sn"].Value = user.Surname;
entryToUpdate.Properties["displayName"].Value = user.DisplayName;
string username = user.Email.Substring(0, user.Email.IndexOf("#"));
entryToUpdate.Properties["sAMAccountName"].Value = username;
entryToUpdate.CommitChanges();
Any Ideas?
I believe
entryToUpdate.Properties["cn"].Value = user.Name;
is your Problem. Use givenName as first Name. "cn" is managed by the System.
Also:
If using Exchange, the Mail attribute is managed by it.
Consider the ramifications of modifing samAccountName. Users may not know how to log in, seperate Systems with pseudo SSO may not recognize them, the Login Name does not match the local Profile Name, etc.
I have an ASP.Net MVC application using Windows Authentication, and I am checking group membership for security on controller actions.
Simple as it sounds, I've found no other Question that can resolve the problem I am experiencing.
First Attempt: [Authorize]
The classic method is to simply slap an Authorize data annotation attribute on the controller action and go to town:
[Authorize(Roles = #"domain\groupName1")]
No dice. I am prompted for credentials. Usually this means something is wrong with the Windows Authentication configuration but it's setup fine: (1) HttpContext.User is a WindowsPrincipal object, and (2) I confirmed another known group name works.
Second Attempt: IsInRole()
The next step taken was to go a more old fashioned route and use IPrincipal.IsInRole(), and again, one returns false, the other true.
var wp = (WindowsPrincipal)User;
// false
var inGroup1 = wp.IsInRole(#"domain\groupName1");
// true
var inGroup2 = wp.IsInRole(#"domain\groupName2");
Stumped... so I hit up my systems nerds and we double check everything. User is a group member? Yes. Group name is spelled correctly? Yes. The next step was to snag the SID.
Third Attempt: Search Identity's Group Collection
In my controller I check the WindowsIdentity and look through the group collection for the SID of the troublesome group:
var wi = (WindowsIdentity)wp.Identity;
var group = wi.Groups.SingleOrDefault(g => g.Value == "group1-sidValue");
The group variable is the SecurityIdentifier object. Because it is not null, we can be certain that this current user is a member of the group that both the [Authorize()] or IsInRole() attempts fail to confirm.
Fourth Attempt: DirectoryServices.AccountManagement
At this point, I'm going nuts and add reference to the AccountManagement APIs. I search the domain context for the GroupPrincipal by both name and SID:
var pc = new PrincipalContext(ContextType.Domain, "domain");
var gp1byName = GroupPrincipal.FindByIdentity(pc, "groupName1")
var gp1bySid = GroupPrincipal.FindByIdentity(pc, IdentityType.Sid, "group1-sidValue");
Both group principal variables are ripe with the same object, and I verified through a watch variable that the principal's Members collection contains a UserPrincipal object with the same SID as the current WindowsPrincipal on HttpContext.
Question:
What in the hell have I missed here? Why would both role checking methodologies fail when it is plain and clear through object exploration that the user is a valid member of this given group?
The fact that one group checks fine and the other does not seems the most strange part at this point.
Answer:
Essentially it's translation issues between WindowsIdentity and NTAccount (both of these System.Security.Principal) and lastly, the actual Active Directory entry.
When validating a WindowsIdentity against AD, if you want to use anything other than the Sam or the Sid, you will need to use System.DirectoryServices.AccountManagement.
Caveat: In .Net 4.5 the security principals include Claims but that's out of context.
Long Explanation:
In a Windows Authenticated web application, HttpContext.User is a WindowsPrincipal object wrapping an underlying WindowsIdentity.
WindowsIdentity has for most intents and purposes only two properties with which the authenticated user can be identified: Name and User.
These properties translate to two properties on the identity's corresponding AD account entry:
WindowsIdentity.Name = SamAccountName
WindowsIdentity.User = SID
The [Authorize] filter attribute ultimately calls IsInRole(string role) on the underlying principal... and the IsInRole() string overload instantiates an NTAccount with the role (the "SamAccountName" in an AD entry).
This explains the failure in #1 and #2 above.
To authorize the HttpContext.User against anything but his/her Sid or SamAccountName, you'll need DirectoryServices.AccountManagement or classic LDAP.
I have an ASP.Net MVC application using Windows Authentication, and I am checking group membership for security on controller actions. Simple as it sounds, I've found no other Question that can resolve the problem I am experiencing.
Took me a lot of time to find something
http://www.c-sharpcorner.com/uploadfile/scottlysle/test-for-user-group-membership-in-Asp-Net-C-Sharp/
My code to check if user belongs to an AD group :
foreach (System.Security.Principal.IdentityReference group in System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
{
if (String.Equals(group.Translate(typeof(System.Security.Principal.NTAccount)).ToString(), #"your_domain_name\your_group_name", StringComparison.InvariantCultureIgnoreCase))
{
// the user belongs to a group
}
}
That is all I needed beside <authentication mode="Windows"/> in Web.config file