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 have been having trouble with my production site (not my development sites). Every now and then both Firefox and Chrome fail to log users in (all users both on our client network and general web). But the strange part is that Internet Explorer always works correctly and has NEVER failed once (I have delete cache and cookies in browsers but still the same thing happens).
Then after an hour or X amount of time, Firefox and Chrome start behaving normally again.
I have a narrowed it down to function below that always returns false even after login.
public bool isLoggedIn()
{
return System.Web.HttpContext.Current.User.Identity.IsAuthenticated;
}
So the process goes below with the user going to login with this function:
public void Login_OnClick(object sender, EventArgs args)
{
string email = UserName.Text;
string password = Password.Text;
string errorMsg = string.Empty;
bool cb = cb_agreeterms.Checked;
if (tests)
{
// The code in here tests to see if email, password, etc. have been filled out.
// This works 100% of the time and is NOT a problem.
}
else
{
// Validate user.
if (Membership.ValidateUser(email, password))
{
// Get the logged in user
MembershipUser user = Membership.GetUser(email);
if (user.IsLockedOut)
{
user.UnlockUser();
}
// Gets a datatable of the user details in our general database
DataTable dtUserData = this.dbData.GetUserByEmail(user.UserName);
if (dtUserData.Rows.Count > 0)
{
FormsAuthentication.SetAuthCookie(user.UserName, true);
// The details for the userId, screenName, etc. below get set by looking at the row 0 in datatable
// The LoginSession function intializes a session with a guid and saves all the data into an Application Context. This creates a SessionGuid cookie which I see get created on FF and Chrome (and always on IE).
LoginSession(userId, screenName, permissionLevel, user.UserName);
Response.Redirect("../myinternalsite.aspx");
}
}
else if (UserExistsInMembership(email))
{
// Tested this out and entering bad credentials fails the login and error is shown correctly on screen in the login control.
// We have failed to login.
ShowLoginError("E-mail or password is incorrect.");
}
}
}
So when the user authenticates, the redirect goes to ../myinternalsite.aspx. On the page in Page Load a VerifyLogin function gets called and calls:
public bool isLoggedIn()
The above ALWAYS returns falso in Chrome and FF which prompts a redirect to the home page. After a couple hours this fixes itself. IE works 100% of the time.
The web.config is this:
// authenticationConnection works and links correctly to the auth database just fine.
<sessionState timeout="120"/>
<membership defaultProvider="SqlProvider">
<providers>
<add connectionStringName="authenticationConnection" applicationName="Auth" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" name="SqlProvider" type="System.Web.Security.SqlMembershipProvider" requiresQuestionAndAnswer="false" passwordFormat="Hashed" enablePasswordReset="true" maxInvalidPasswordAttempts="1000" passwordAttemptWindow="1" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="SqlRoleManager">
<providers>
<add name="SqlRoleManager" type="System.Web.Security.SqlRoleProvider" connectionStringName="authenticationConnection" applicationName="MyApp"/>
</providers>
</roleManager>
<identity impersonate="true"/>
The cookies in Chrome and Firefox get set. I deleted them and saw them get reset correctly. But what is this issue? Why is IsAuthenticated failing for only some browsers and working for others and then fixes itself?
My login template with all my different steps is something like this too:
<asp:UpdatePanel ID="updateTheLogin" runat="server">
<ContentTemplate>
<asp:TextBox ID="UserName" runat="server" CssClass="loginTextbox"></asp:TextBox>
<asp:TextBox id="Password" runat="server" textMode="Password" CssClass="loginTextbox"></asp:TextBox>
<input type="button" class="btn-small pull-right disabled" id="LoginButton" value="Log In" onserverclick="Login_Click" runat="server" />
</ContentTemplate>
</asp:UpdatePanel>
If you use MembershipProvider, you do not need to create Form Authentication cookie by yourself.
I answered one of your question, but after reading this, ignore that answer since you are using Membership Provider which will automatically create IPrincipal object for you.
All you have to do is to use ASP.Net Login control.
<asp:Login ID="Login" runat="server"></asp:Login>
Note: applicationName should be same for both membership and roleManager. They are different in your web.config.
How to View Authenticated User's Information
protected void Page_Load(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
var sb = new StringBuilder();
var id = (FormsIdentity) User.Identity;
var ticket = id.Ticket;
sb.Append("Authenticated");
sb.Append("<br/>CookiePath: " + ticket.CookiePath);
sb.Append("<br/>Expiration: " + ticket.Expiration);
sb.Append("<br/>Expired: " + ticket.Expired);
sb.Append("<br/>IsPersistent: " + ticket.IsPersistent);
sb.Append("<br/>IssueDate: " + ticket.IssueDate);
sb.Append("<br/>Name: " + ticket.Name);
sb.Append("<br/>UserData: " + ticket.UserData);
sb.Append("<br/>Version: " + ticket.Version);
Label1.Text = sb.ToString();
}
else
Label1.Text = "Not Authenticated";
}
I am attempting to re-do a login system using Claims-Based auth.
So far, so good
Stepping through, it appears to correctly evaluate a username and password and correctly create a claims principal (including adding an authentication type in order to set IsAuthenticated to true, per this SO question.)
But then...
Somehow the identity doesn't seem to correctly get set on the wire. As a result, I'm redirected directly back to the login page.
The Code
I have the following in global.asax:
private void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var currentPrincipal = ClaimsPrincipal.Current;
var transformer = new ClaimsTransformer(); //My own custom transformer; code below.
var newPrincipal = transformer.Authenticate(string.Empty, currentPrincipal); // does the transformation
// as I understand, it is proper & recommnded to set both of these
Thread.CurrentPrincipal = newPrincipal;
HttpContext.Current.User = newPrincipal;
}
In my Login Controller, I have a simple test against a membership DB. I verified while debugging that this has newCP as a valid, authenticated identity that has the expected name.
[HttpPost]
[AllowAnonymous]
public ActionResult UserLogin(LoginViewModel viewModel)
{
var loginSuccess = Membership.ValidateUser(viewModel.UserName, viewModel.Password);
if (loginSuccess)
{
// CustomApplicationIdentity puts some identity-based logic into business domain terms and uses Claims underneath.
//Should have done it at the IPrincipal level, but instead I created the ToAuthenticatedStandardClaimsIdentity() which returns a new authenticated ClaimsIdentity.
var newIdentity = new CustomApplicationIdentity(viewModel.UserName);
var cp = new ClaimsPrincipal(newIdentity.ToAuthenticatedStandardClaimsIdentity());
var newCP = new ClaimsTransformer().Authenticate(string.Empty, cp);
System.Web.HttpContext.Current.User = newCP;
Thread.CurrentPrincipal = newCP;
if (!string.IsNullOrWhiteSpace(viewModel.ReturnUrl))
{
return Redirect(viewModel.ReturnUrl);
}
return RedirectToAction("Index", "Identity");
}
}
The Problem
When it redirects to the Action, I see it hit the Application_PostAuthenticateRequest again, which makes perfect sense.
However, despite previously setting the principal, this now appears to be an empty principal (no name, with IsAuthenticated set to false).
Where am I Going Wrong?
Some thoughts:
Is it because I haven't set up the SessionSecurityToken yet?
Am I completely missing something regarding threading or setting the context correctly?
Since the UserLogin method is in MVC, I also tried using controller context, but that didn't seem to work either.
Is it possible that something else could be messing with this in the middle?
Read: Is there an easy way to verify that some portion of the old login system isn't left over and toying with me?
After a ton of research (and wading through the excellent Pluralsight Course by Dominick Baier), the solution was the following:
The Over-arching Big Steps / Problems
I wasn't setting a session authentication cookie, so the redirect was being treated as a new request, which saw no cookie and didn't set a principal.
Later, when I used a session authentication manager, it turns out that Cassini (VS's built in debug server) wasn't loading the SessionAuthenticationManager at all
(IIS and IIS Express do so just fine).
The Full Solution
Step by step (again, much of this is credited to Dominick's video):
Step 1: Add identity services to config
Right-click on your project, and select "Add Reference..."
In the Framework section, select System.IdentityModel.Services
Add the following to your web.config:
(full outline below, insert the two sections within that outline in your web.config):
<configuration>
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</configSections>
</configuration>
Step 2: Add session authentication manager
(which depends on that config setting)
In the system.webServer section of your web.config, add the following lines:
<remove name="RoleManager"/> <!--Not needed anymore in my case -->
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
Step 3: Remove the PostAuthenticate method in Global.asax
(no longer needed because of SAM, which detects the cookie; why run it on every request if you don't have to, right?)
Step 4: Set your Claims Transformation method to set the authentication cookie
Add these lines in your ClaimsAuthenticationManager (mine was called ClaimsTransformer). I put this in a separate method called "EstablishSession", which took in my principal after it was already transformed:
private void EstablishSession(ClaimsPrincipal transformedPrincipal)
{
var sessionToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
}
So now the cookie is always set whenever you transform a claim, which makes sense because you're only transforming a claim if the user was successfully authenticated.
Step 5: Tear your hair out for a bit...
...wondering why SessionAuthenticationManager is always null.
Seriously, everything seems to work, and your config is correct, but darn it if it isn't null every. single. time.
Step 6: Switch the debug web server to IIS Express
Ahhhh, it appears that Cassini (the build in VS Debugger) doesn't work with SessionAuthenticationManager.
However, IIS Express does. Switch it to that in your project settings.
And Voila!
Now I have a page that works.
I think you need to implement the SessionSecurityToken, or something that will persist your session between page requests. Here is a more custom type of approach:
public static int SetAuthCookie(this HttpResponseBase responseBase, User user, bool rememberMe)
{
// Initialize Session Ticket
var authTicket = new FormsAuthenticationTicket(1
, user.Email
, DateTime.Now
, DateTime.Now.AddHours(30)
, rememberMe
, JsonConvert.SerializeObject(new {
Email = user.Email,
FirstName = user.FirstName,
Id = user.Id
})
, FormsAuthentication.FormsCookiePath);
var encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
if (authTicket.IsPersistent)
authCookie.Expires = authTicket.Expiration;
responseBase.Cookies.Add(authCookie);
return encTicket.Length;
}
public static void VerifyAuthCookie(HttpContext context)
{
HttpCookie authCookie = context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
return;
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket == null)
return;
if (authTicket.Expired)
return;
User user = !string.IsNullOrEmpty(authTicket.UserData) ? JsonConvert.DeserializeObject<User>(authTicket.UserData) : null;
if (user == null)
return;
// Create an Identity object
UserIdentity id = new UserIdentity(user, authTicket);
// This principal will flow throughout the request.
GenericPrincipal principal = new GenericPrincipal(id, new [] { "User" });
context.User = principal;
}
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
MembershipService.ChangePassword doesn't change password issue. I have no clue why...
var userUsers = from n in db.aspnet_Users where n.UserId == id select n;
string userName = userUsers.Single<aspnet_Users>().UserName;
MembershipUser user = Membership.GetUser(userName, false);
if (user != null)
{
string generatedPassword = user.ResetPassword();
if (MembershipService.ChangePassword(userName, generatedPassword, model.NewPassword))
{
// So it doesn't change the password
Check this out in the documentation:
ChangePassword returns true if the password was updated successfully.
Otherwise, it returns false.
Before changing a password, ChangePassword calls the provider's overridable OnValidatingPassword method to validate the new password. It then changes the password or cancels the action based on the outcome of the call.
If the user name, password, new password, or password answer is not
valid, ChangePassword does not throw an exception; it simply returns
false.
Following a successful password change, ChangePassword updates the user's LastPasswordChangedDate.
This helped me once when I had the exact same problem. The password was not valid so the method never actually changed it. I changed some password configuration in the web.config file and then it finally worked.
Check this lines of your web.config, this might be the problem:
<membership defaultProvider="Demo_MemberShipProvider">
<providers>
<add name="Demo_MemberShipProvider"
type="System.Web.Security.SqlMembershipProvider"
connectionStringName="cnn"
enablePasswordRetrieval="false"
**enablePasswordReset="true"**
**requiresQuestionAndAnswer="true"**
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="5"
**minRequiredPasswordLength="5"**
**minRequiredNonalphanumericCharacters="0"**
passwordAttemptWindow="10" passwordStrengthRegularExpression="">
</providers>
</membership>
Hope it helps!