I´m building a web application which could get accessed in two ways. Everyone who is working in the same organisation as I can use our active directory to access the application.
Everyone from outside should join the application through a separate membership database. Everyone should have a account in the membership database with his roles, so the ad connection is just a bonus to make it easier to keep the password and username in mind.
I searched the internet but couldn't find a comparable situation. This is my first time working with ad.
Does anyone know of a framework that can be used or give me a hint on how I could try to solve the problem?
At moment I implemented the membership connection with System.Web.WebData.SimpleMembershipProvider and it works fine.
In the later development of the application I also need some other connections to the ad to check some information but that is just a problem for another day.
Thanks for the help.
Open up your web.config.
First of all you'll need connectionString for your ActiveDirectory:
<connectionStrings>
...
<add name="ADConnectionString" connectionString=LDAP://*adserver*/DC=*domain* />
...
</connectionStrings>
Scroll down to the <membership> tag. Make sure you have defaultProvider attribute set for the <membership>, like:
<membership defaultProvider="SimpleMembershipProvider">
Then add new provider for AD members inside <providers>:
<add name="ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="ADConnectionString" attributeMapUsername="sAMAccountName" />
That should do the trick for web.config. Now we need to auth AD users on Log in. Go to your AccountController Login action. First we try to authenticate user via ActiveDirectory, there is handy class called PrincipalContext in System.DirectoryServices.AccountManagement namespace. If that fails we use the default membership provider:
public ActionResult Login(LoginModel model, string returnUrl)
{
try
{
// try to auth user via AD
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain))
{
if (pc.ValidateCredentials(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
return RedirectToAction("Index", "Home");
}
}
// try the default membership auth if active directory fails
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "Login failed");
}
}
catch
{
}
GetErrorsFromModelState();
return View(model);
}
For your later requirements you can get the current logged in ActiveDirectory user with UserPrincipal class:
using (var context = new PrincipalContext( ContextType.Domain))
{
using (var aduser = UserPrincipal.FindByIdentity( context,IdentityType.SamAccountName, HttpContext.User.Identity.Name))
{
...
}
}
Hope this helps and I didn't miss anything.
This Code will give you if the user with specified username and password is valid
public bool ValidateUser(string userName, string password)
{
bool authenticated = false;
string dePath = string.Empty;
dePath += DomainController;
if (!string.IsNullOrEmpty(BaseDomainName))
{
dePath += "/" + BaseDomainName;
}
try
{
DirectoryEntry entry = new DirectoryEntry(dePath, userName, password);
object nativeObject = entry.NativeObject;
authenticated = true;
}
catch
{
return false;
}
return authenticated;
}
You can add DomainController and BaseDomainName in web.config appSettings as keys
Related
Problem Statement
I've some applications running and potential users are same for both applications so right now they need to signup on both applications separately and create password for both.
If someone forget the password or someone want to reset his/her password they need to perform action on both applications.
What I want?
I want to create a different database for authentication and want to use that database for both application at the time of authenticating user.
Is it possible using same approach I am using Entity framework how can i use to open a connection with different database(Central DB). Is there any tutorial or any link that could help me ? what should be the best approach to implement this ?
My Current code
public ActionResult Login(Login login)
{
var result = user.FindUser(login.Email);
if (result==null)
{
ViewData["Con"] = "IncorrectEmail";
ViewBag.data = "login";
return View();
}
if(result.Password==null || result.ConfirmPassword==null)
{
ViewData["Con"] = "PasswordNull";
ViewBag.data = "login";
return View();
}
var deptName = deptRepos.SelectByID(result.DepartmentID);
var role = (roleRep.SelectByID(result.RoleID).RoleType).ToUpper();
string pass = user.Decrypt(result.Password);
if (login.Password.ToLower().Trim() != pass || login.Password==null)
{
ViewData["Con"] = "IncorrectPassword";
ViewBag.data = "login";
return View();
}
//System.Web.HttpContext.Current.Session["UserName"] = result.Name;
System.Web.HttpContext.Current.Session["Email"] = login.Email;
System.Web.HttpContext.Current.Session["Password"] = login.Password;
System.Web.HttpContext.Current.Session["Department"] = deptName.Name;
System.Web.HttpContext.Current.Session["DepartmentID"] = deptName.ID;
System.Web.HttpContext.Current.Session["Designation"] = result.Designation;
System.Web.HttpContext.Current.Session["Role"] = role;
this is my webconfig
<connectionStrings>
<add name="EmailConfiguration" connectionString="data source=.; initial catalog=EmailConfiguration;persist security info=True; MultipleActiveResultSets=true; Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
This is the current scenario with single DB.
I'm wondering what the proper way is to handle SQL permissions and store connection strings is in a C# MVC application.
Up until now I have done work around Windows authentication, so it has been set as such in the web.config connection string to use windows authentication and all database calling and writing has been done via the database context referencing that.
<add name="ConnectionStringName" connectionString="data source=SqlDB; initial catalog=DBNAME;persist security info=True; Integrated Security=SSPI;" providerName="System.Data.SqlClient"/>
public class DatabaseContext : DbContext
{
public DatabaseContext() : base("name = ConnectionStringName")
{
}
... ...
controller code sample
public class MyController : Controller
{
public DatabaseContext db = new DatabaseContext();
// GET:
public ActionResult myView()
{
NewMmodel model = new NewModel();
model.table = db.table.ToList();
return View(model);
}
[HttpPost]
public ActionResult Add([Bind(Include ="MyModel")] Add model)
{
db.Database.ExecuteSqlCommand("EXEC dbo.usp....
I now need to make something that people login with a username and password and will pull permissions from there.
My first thought would have been to make the web.config connection string "Dynamic" but it doesn't seem that this is possible? so probably not the proper way of doing it even if it is then?
Is it same to store a persisted username and password in a main connection string to allow initial reading from a database? if so is it then best to create a connection string in a class?
so in a data access layer class have something like:
public ProcessContext() : base(ConnectionString())
{
}
private static string ConnectionString()
{
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder();
sqlBuilder.DataSource = "XXX";
sqlBuilder.InitialCatalog = "YYY";
sqlBuilder.PersistSecurityInfo = true;
sqlBuilder.IntegratedSecurity = false;
sqlBuilder.MultipleActiveResultSets = true;
sqlBuilder.UserID = "xxx";
sqlBuilder.Password = "yyy";
return sqlBuilder.ToString();
}
I'm getting an error with this though saying ProcessContext() Must have a return type
If I am on the right track, finally what I want to be able to do is replace the USERID and password values above with the ones the user has logged in with.
I'm using the pre-built code visual studio pulls in with mvc 5 for the manual login.
I believe it comes from the SetupAuth.cs file and users the tables like AspNetUsers, roles Logins & Claims that it creates itself.
I can't really see where you can access those session values, I just need to add the [Authorize] tag above a call in the controller to check for a correct login.
Hope this all makes sense! Is this Owin/OAuth? everything i try to look at regarding OAuth talks about google or facebook login...
I have an existing ASP.NET application that uses LDAP for authentication and ASP.NET membership for authentication and authorization.
So an LDAP user could choose to authenticate either using his LDAP credentials, or ASP.NET membership credentials. A non LDAP user can only authenticate using LDAP credentials.
I now want to create a Web API project that uses a similar approach for authentication and authorization.
Using VS 2013, I created a new Web API project that uses the Individual Accounts option for authentication.
I've modified the GrantResourceOwnerCredentials method in the Providers\ApplicationOAuthProvider.cs file.
Before
...
IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
...
After
...
IdentityUser user;
if (AuthenticateActiveDirectory(context.UserName, context.Password, "MyADDomain"))
{
user = await userManager.FindByNameAsync(context.UserName);
}
else
{
user = await userManager.FindAsync(context.UserName, context.Password);
}
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
...
And the AuthenticateActiveDirectory method is:
private bool AuthenticateActiveDirectory(string userName, string password, string domain)
{
bool validation;
try
{
var lcon = new LdapConnection(new LdapDirectoryIdentifier((string)null, false, false));
var nc = new NetworkCredential(userName, password, domain);
lcon.Credential = nc;
lcon.AuthType = AuthType.Negotiate;
lcon.Bind(nc);
validation = true;
}
catch (LdapException)
{
validation = false;
}
return validation;
}
This works, but is it the best way of doing it or is there a better way?
It's fine. Couple things come to mind:
If you have multiple domains in your forest (or ever will support that), username isn't unique across the forest - just the domain. Looks like you're keying on username today.
For your LdapConnection, I assume the null server name is just for show here? Rather than hardcoding one, you can use the S.DS.AD namespace to find a domain controller for you.
You'd probably want AuthType.Basic rather than Negotiate here. You'll want to make sure your connection is LDAP/S since the creds will be cleartext.
I am trying to access user's password hash using DNN API. Please see below code segment
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage Auth()
{
UserInfo u = DotNetNuke.Entities.Users.UserController.GetUserByName("user1");
string hostPassword = DotNetNuke.Entities.Users.UserController.GetPassword(ref u, String.Empty);
return Request.CreateResponse(HttpStatusCode.OK, hostPassword);
}
But I am getting following error, saying hashed passwords can't be retrieved
Parser Error Message: Configured settings are invalid: Hashed passwords cannot be retrieved. Either set the password format to different type, or set enablePasswordRetrieval to false.
This is my configuration section
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SiteSqlServer" enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="false" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="0" requiresUniqueEmail="false" passwordFormat="Hashed" applicationName="DotNetNuke" description="Stores and retrieves membership data from the local Microsoft SQL Server database" />
Can anyone explain what is the wrong with my code or configuration.?
Further, If I want to authorize users I would I do it in DNN web services?
Found a reliable way to authenticate users in DNN.
It is possible to authenticate a DNN user in the following way.
System.Web.Security.Membership.ValidateUser(username,password)
Retrieving hased passwords is not allowed in DNN and it is not a good practice too. That's the reason for the error message for my code.
Maybe a more reliable alternative is to check this within DNN infrastructure:
public static bool IsValidUserAndPassword(string username, string password, out UserInfo user)
{
UserLoginStatus status = UserLoginStatus.LOGIN_FAILURE;
user = DotNetNuke.Entities.Users.UserController.ValidateUser(PortalSettings.Current.PortalId, username, password, "", PortalSettings.Current.PortalName, HttpContext.Current.Request.UserHostAddress, ref status);
return user != null && user.UserID != -1 && status == UserLoginStatus.LOGIN_SUCCESS;
}
Usage:
UserInfo user;
if(IsValidUserAndPassword("username", "password", out user))
{
// User is valid
// Do whatever stuff here...
}
I am using the standard Simple Membership model for login via forms in my application. I would like to provide the possibility to login via AD as an alternative.
When logging in via AD, the process should be as follows:
Check that AD authenticates the user, but do not use the information for the principal.
Check if any local user exists with the provided Active Directory username (I have a property on my UserProfile model named ActiveDirectoryID).
If it exists, perform a local login using the local username for this UserProfile.
The problem: I cannot retrieve the local password, so in order to login locally after AD authentication, I need to be able to force the login without the password.
I've considered the following strategies:
Create an extension method for Websecurity to allow Websecurity.Login(string username)
Somehow set the logged in user manually, without implicating Websecurity.
Is this doable / feasible? Is it possible for the framework to create the necessary auth cookie without the plaintext password? And how would I do this?
SOLUTION:
This ended being the correct solution:
public ActionResult ActiveDirectoryLogin(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://DC=MyIntranet,DC=MyCompany,DC=com", model.UserName, model.Password);
object NativeObject = entry.NativeObject;
var internalUser = db.UserProfiles.Where(x => x.ActiveDirectoryID == model.UserName).SingleOrDefault();
if (internalUser != null)
{
FormsAuthentication.SetAuthCookie(internalUser.UserName, model.RememberMe);
return RedirectToLocal(returnUrl);
}
}
catch (DirectoryServicesCOMException)
{
// No user existed with the given credentials
}
catch (InvalidOperationException)
{
// Multiple users existed with the same ActiveDirectoryID in the database. This should never happen!
}
}
return RedirectToAction("Login");
}
This is all that the Websecurity.Login method does:
public static bool Login(string userName, string password, bool persistCookie = false)
{
WebSecurity.VerifyProvider();
bool flag = Membership.ValidateUser(userName, password);
if (flag)
{
FormsAuthentication.SetAuthCookie(userName, persistCookie);
}
return flag;
}
You can write your own method that authenticates against AD and then looks up the user name and the does sets the auth cookie something like:
public static bool MyLogin(string userName, string password, bool persistCookie = false)
{
bool flag = CheckADUser(userName, password);
if (flag)
{
string mappedUsername = GetMappedUser(userName);
if(mappedUsername != "")
{
FormsAuthentication.SetAuthCookie(userName, persistCookie);
}
else
{
flag = false;
}
}
return flag;
}
Hope this helps.