System.Web.HttpContext.Current.User.Identity.IsAuthenticated fails sometimes - c#

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";
}

Related

Session_End in Global.asax.cs not firing

I have an Asp.net web application where I am using FormsAuthentication for User login.
I want to prevent multiple logins to the same user at the same time.
For this I have set the FormsAuthentication timeout to 15 minutes and Session.timeout to 15 minutes.
When the user closes the browser without logging out, or if the user is inactive for 15 minutes, it is not firing the Session_End() event in global.asax.cs file. I want to update the database field in the Session_End() event.
Code for Login:
if (Membership.ValidateUser(username, password))
{
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
FormsAuthentication.HashPasswordForStoringInConfigFile(password, "SHA1"));
// Now encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
// Create a cookie and add the encrypted ticket to the cookie as data.
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
context.Response.Cookies.Add(authCookie);
context.Response.Redirect("/HomePage", false);
}
Global.asax.cs:
protected void Session_Start(Object sender, EventArgs e)
{
Session["init"] = 0;
Session.Timeout = 15;
}
protected void Session_End(Object sender, EventArgs e)
{
PersonObject person = new PersonObject();
// calling the function to update entry in database
person.ResetUserLoginStatus(HttpContext.Current.User.Identity.Name);
}
Function to update entry in database:
public bool ResetUserLoginStatus( string username="")
{
string sql = "UPDATE Person SET IsLogged=0 WHERE Person = #Person";
PersonObject person = new PersonObject();
object id = person.ExecuteScalar(sql, new Dictionary<string, object>() {
{ "Person", (!string.IsNullOrEmpty(username)?username:User.Name )}
}, "Person");
return true;
}
Web.config:
<authentication mode="Forms">
<forms loginUrl="/Security/Login.ashx/Home" name="SecurityCookie" timeout="15" slidingExpiration="true">
</forms>
</authentication>
<sessionState timeout="15" mode="InProc"></sessionState>
The problem is that when the browser is closed the ResetUserLoginStatus() method isn't called and I am unable to reset my value to 0. Since the field has not been reset to 0, that user won't be able to log in again.
Please suggest.
Session_End is actually not that useful or reliable. For one thing, it only fires at the end of the pipeline processing when an HTTP request has been received and a response has been rendered. That means it does NOT fire for a user who has simply closed their browser. Also, the event will never fire except for certain types of session state-- it won't work with State Server, for example, or SQL-based session state. The bottom line is you can't rely on it to maintain an unambiguous "Is logged in" flag.
Instead, I would store a "last page request received" time stamp. You can then infer the value of a "is logged in" flag; any user who has submitted a request in the past 15 minutes is still logged in.

session timeout in asp.net mvc when refresh

I am running an ASP.NET 4.0 application and published it using plesk control panel.I have done the following.
1.i set session timeout in web.config file as:
<sessionState timeout="20000"/>
I handled session variable in login controller like this:
Session["userId"] = lUser.userId;
Session["role"] = lUser.userType;
into other controller, the code is like below:
if (Session["role"] == null)
{
return RedirectToAction("Index", "Login");
}
else if (Session["role"].ToString() == "Admin" || Session["role"].ToString() == "Super Admin")
{
return View();
}
this code is ok in my local server but when published into real server using plesk control panel, it also ok for first time. but when i click the same menu second time it redirects to login page.
Try adding:
protected void Session_Start(Object sender, EventArgs e)
{
Session["init"] = 0;
}
to global.asax
Instead of SessionState mode="InProc" use SessionState Mode="StateServer", but you'll need to make sure the server where you're hsoting the application has the StateServer active.
<sessionState mode="StateServer" timeout="20000" cookieless="false" />

asp.net custom user authentication with master page

I am working on a small Inventory management system. I have almost created it on my local machine. It works as it is designed. I just moved it to my hosting account and now I am facing some problems.
1, After login, when user goes to different pages, open and closes forms, after some time it redirects to login page. I don't know why. Seems like some sort of exception was occurred or the session had gone empty. How to handle this condition?
2, Is this the right way to keep a check on the user login using a check in Page_Load event of master page?
My site uses a master page that has separate layout for top navigation menu and bottom body area. When the first time user lands on the site it login into the system and upon successful login I store his information in session. I am heavily using Session in all pages for ADD, DELETE, UPDATE purposes. When an add record is performed, I pass success of failure message in session to show after post back. Code behind of login page is given below:
protected void loginForm_OnAuthenticate(object sender, AuthenticateEventArgs e)
{
string error = "";
sMethodName = "loginForm_OnAuthenticate";
_objLoginBLL = new LoginBLL();
int iRetVal = _objLoginBLL.ValidateUser(loginForm.UserName, loginForm.Password, ref error);
if (iRetVal >= 0)
{
Session.Clear(); //Remove all stored Session variables.
Session[Constant.Session.LOGGED_IN_DATETIME] = DateTime.Now.ToString("yyyyMMddHHmmssfff");
Session[Constant.Session.LOGIN_USERNAME] = loginForm.UserName;
Session[Constant.Session.LOGIN_USER_ID] = iRetVal;
Session[Constant.Session.LOGIN_COMPANY] = ddlCompanies.SelectedValue;
Session[Constant.Session.LOGIN_FISCAL_YEAR] = ddlFiscalYear.SelectedValue;
Session[Constant.Session.IS_DIRECT_ACCESS] = "NO";
FormsAuthentication.RedirectFromLoginPage(loginForm.UserName, loginForm.RememberMeSet);
}
else
{
Logger.Log("User validation failed.", sClassName, sMethodName, DEBUG);
switch (iRetVal)
{
case -1:
loginForm.FailureText = Constant.Messages.INCORRECT_USER_OR_PASSWORD;
loginForm.Focus();
break;
case -2:
loginForm.FailureText = Constant.Messages.ACCOUNT_LOCKED;
loginForm.Focus();
break;
//case -3:
//TODO: Account doesn't exists
default:
var randToken = new Random().Next(1000);
Session[Constant.Session.TOKEN] = randToken;
var myHashtable = new Hashtable
{
{Constant.Session.TOKEN, randToken},
{Constant.Fields.ERROR_KEY, iRetVal}
};
Response.Redirect(WebFunctions.CreateQueryString(Constant.Urls.Error, myHashtable));
break;
}
}
}
I am continuously checking if the session doesn't contain any user id then redirect it to the login page. The code behind of my master page is given below:
protected void Page_Load(object sender, EventArgs e)
{
if (Session[Constant.Session.LOGIN_USER_ID] == null)
{
FormsAuthentication.RedirectToLoginPage();
return;
}
CheckDBConnection();
Initialize();
}
Any help or tips will be appreciated.
You can view the site here: www.paracha.net (I can share guest account credentials in private message if anyone is interested)
First of all, keep in mind that the session cookie is not encrypted, so you should not be using the session to store any confidential information.
Secondly, you should not be checking the authentication on every Page_Load. Instead, you should configure the page access in web.config:
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
This will protect all your pages so only authenticated (i.e. logged in) users will see the page, while all others will be redirected to the login page.
If you have some pages (e.g. a splash page) or folders (e.g. the images folder) that you want them to be accessible to all users, then add a section for each page or folder:
<configuration>
<location path="splash.aspx">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
<location path="images">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
</configuration>
In order for this to work, you should be using forms authentication. Here are the settings in web.config:
<configuration>
<system.web>
<authentication mode="Forms">
<forms
name=".YOURNAME_AUTH"
loginUrl="login"
defaultUrl="/"
protection="All"
timeout="30"
path="/"
requireSSL="true"
slidingExpiration="true"
cookieless="UseCookies"
domain=""
enableCrossAppRedirects="false">
</forms>
</authentication>
</system.web>
</configuration>
Obviously, you will need a login.aspx page, and when you click the Log in button, you need to authenticate the user like this:
protected void btnLogIn_Click(object sender, EventArgs e) {
string Username = txtUsername.Text;
string Password = txtPassword.Text;
try {
if (ValidateUser(Username, Password)) {
FormsAuthentication.RedirectFromLoginPage(Username, false);
}
else {
lblMessage.Text = "Incorrect Credentials.";
lblMessage.ForeColor = Color.Red;
}
}
catch {
lblMessage.Text = "Login Failed.";
lblMessage.ForeColor = Color.Red;
}
}
The function ValidateUser() can do anything you want for authentication. You can validate the credentials against your database if you like.
If you use FormsAuthentication, you do not need to check Session[Constant.Session.LOGIN_USER_ID] manually. It will redirect to Login page automatically for which you can configure in web.config.
Another thought
It is not directly related to your question. It is just an alternative approach.
Instead of creating multiple session states, you can create custom Context to keep track of the current logged-in user's information
E.g. You can store Company and Fiscal Year properties inside MyUser class.
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null &&
HttpContext.Current.User.Identity.IsAuthenticated)
{
MyContext.Current.MyUser =
YOUR_BLL.GetUserByUsername(HttpContext.Current.User.Identity.Name);
}
}
public class MyContext
{
private MyUser _myUser;
public static MyContext Current
{
get
{
if (HttpContext.Current.Items["MyContext"] == null)
{
MyContext context = new MyContext();
HttpContext.Current.Items.Add("MyContext", context);
return context;
}
return (MyContext) HttpContext.Current.Items["MyContext"];
}
}
public MyUser MyUser
{
get { return _myUser; }
set { _myUser = value; }
}
}
}
Addition
C# is strongly type language, so you should not encode the variable name with type of variable. E.g. objLoginBLL and iRetVal. Please read C# Design Guideline or Essential C# 6.0 (Page 7).

Accessing User Password in DNN API

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...
}

Check authentication ticket expiration without affecting it

I am trying to implement a Web Application Project where my web pages can check the server for the Authentication ticket expiration date/time using AJAX.
I am using Forms Authentication with slidingExpiration.
The problem I run across is I can't figure out how to check the value without resetting it. I created a simple page - CheckExpiration.aspx - below is the code behind:
private class AjaxResponse
{
public bool success;
public string message;
public string expirationDateTime;
public string secondsRemaining;
public string issueDate;
}
protected void Page_Load(object sender, EventArgs e)
{
AjaxResponse ar = new AjaxResponse();
JavaScriptSerializer js = new JavaScriptSerializer();
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
string expiration = id.Ticket.Expiration.ToString();
TimeSpan timeRemaining = id.Ticket.Expiration - DateTime.Now;
ar.success = true;
ar.expirationDateTime = expiration;
ar.issueDate = id.Ticket.IssueDate.ToString();
ar.secondsRemaining = timeRemaining.Minutes.ToString() + ":" + timeRemaining.Seconds.ToString();
}
else
{
ar.success = false;
ar.message = "User not authenticated";
}
string output = js.Serialize(ar);
Response.Write(js.Serialize(ar));
}
I call this page from the Master page in my application using ajax every second. Past the halfway point in the authentication expiration, the expiration gets reset.
How do I prevent this behavior? Is there anything I can do in the header of the request maybe?
Why don't you store the expiration as a session variable that you compute yourself? You only need to get the value of id.Ticket.Expiration once. Then each call, get the value from the server and increment it accordingly, and store it back on the server.
http://msdn.microsoft.com/en-us/library/ms178581%28v=vs.85%29.aspx
Pseudocode:
if(!Session.KeyExists("Expiration"))
{
Session["Expiration"] = id.Ticket.Expiration;
}
Session["TimeRemaining"] = Session["Expiration"] - DateTime.Now;
// get all ajaxy here
Put your CheckExpiration.aspx page in its own application and deploy this as a virtual directory beneath your main application. In that virtual directory, configure slidingExpiration=false. Your code will work as-is but will not regenerate the ticket when it gets below half the time until expiration.
Here's what I did in a quick local project to verify that it works:
Created a new web application AuthTest4 and configured it to use local IIS server in path /AuthTest4
Went into IIS and changed the Machine Key setting for /AuthTest4 to uncheck all the AutoGenerate/Isolate options and generated its own MachineKey.
Created an empty web application ExpCheck and put your CheckExpiration.aspx code in it
Configured ExpCheck web application to use local IIS in the virtual directory /AuthTest4/ExpCheck
Modified the web.config of ExpCheck application to have only the section shown below
ExpCheck web.config. All other security settings will cascade down from the parent virtual directory.
<system.web>
<authentication mode="Forms">
<forms slidingExpiration="false" />
</authentication>
</system.web>

Categories

Resources