Need some help with a Session timeout problem in an ASP.Net web app. Essentially the session expires about 10-15 seconds after login.
Side Note: I use a custom combo of FormsAuthentication and basic security
My Session.IsNewSession gets set to true after 3-4 good postbacks after login.
My Web.Config has the following...
<sessionState mode="InProc" timeout="130" regenerateExpiredSessionId="true" stateNetworkTimeout="120" compressionEnabled="true" cookieless="UseCookies" />
<authentication mode="Forms">
<forms timeout="120" ticketCompatibilityMode="Framework40" enableCrossAppRedirects="true" />
</authentication>
Where I believe the timeout refers to minutes....
I have an MVC 3 application with an ActionFilter registered
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyActionFilterAttribute());
}
Inside the OnActionExecuting I check for a Current Session to prevent access to controller actions which an unauthorized user shouldn't be able to access.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
Player player = SessionHelper.GetCurrentPlayer(filterContext.HttpContext.Session);
if (player == null && ctx != null)
{
player = SessionHelper.GetCurrentPlayer(ctx.Session);
}
if (player == null ||
(player.IsAdministrator == false && player.IsCoach == false && player.IsCommittee == false))
{
if (filterContext.Controller is HomeController || filterContext.Controller is AccountController)
{
base.OnActionExecuting(filterContext);
return;
}
string ctxError = ctx != null ? "Context Exists" : "Context Doesnt Exist";
string sessionError = filterContext.HttpContext.Session != null ? "filterContext.HttpContext.Session Exists" : "filterContext.HttpContext.Session Doesnt Exist";
string requestSessionError = filterContext.RequestContext.HttpContext.Session != null ? "filterContext.RequestContext.HttpContext.Session Exists" : "filterContext.RequestContext.HttpContext.Session Doesnt Exist";
throw new SecurityException(string.Format("Attempt to access {0} by user at {1} - {2} ({3}, {4}, {5})",
filterContext.HttpContext.Request.RawUrl,
filterContext.HttpContext.Request.UserHostAddress,
filterContext.HttpContext.Session,
ctxError,
sessionError,
requestSessionError));
}
base.OnActionExecuting(filterContext);
}
So I've determined that the Web Server was refreshing its AppPool faster than my session lifespan. This would result in the same SessionId to be used, but the IsNewSession flag to be set also.
As I have no control over the AppPool lifespan I was able to keep the session in the InProc mode in IIS.
I resolved the issue by moving Session State persistance to a hosted SqlServer database, thereby allowing the session to persist despite the AppPool being recycled.
I'd recommend the solution for any other person seeing their otherwise stable website losing their session state when hosted on a server which they do not have administrative access to.
Oh, and I found that IIS logs were pretty useless here. The best diagnosis logging here, I found, was my own manual logging to determine that this was the scenario.
Related
I have the following attribute to make sure that the remote site page opens in https mode.
public class RemoteRequireHttpsAttribute : RequireHttpsAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentException("Filter Context");
}
if (filterContext != null && filterContext.HttpContext != null)
{
if (filterContext.HttpContext.Request.IsLocal)
{
return;
}
else
{
string val = ConfigurationManager.AppSettings["RequireSSL"].Trim();
bool requireSsl = bool.Parse(val);
if (!requireSsl)
{
return;
}
}
}
base.OnAuthorization(filterContext);
}
}
Local development now work normal since i don't want it to open in https mode.
Dev site opens the page in https mode - no issues here (single node).
Where as the production (load balanced - 2 nodes) site that i am currently setting up is giving me following error. Please note that dev and prod sites have the same setings and web.config
The page isn't redirecting properly
Firefox has detected that the server is redirecting the request for this address in a way that will never complete.
This problem can sometimes be caused by disabling or refusing to accept cookies.
Dev site url is like
http://dev.datalab.something.org
Prod site url is like
http://datalab.something.org
And here is the call
[RemoteRequireHttps]
public ActionResult Index(string returnUrl, string error)
What am i missing here?
Update 1: My admin has confirmed that the SSL termination has been setup at the lad balancer evel. I have looked at the iis site setup and i don't see https bindings. I only see http binding. Does he need to setup https bindings as well?
Update 2: #AlexeiLevenkov pointed me to the right direction and this post had the code that I utilized and it is working. MOVED the code into a separate answer.
Your site is behind load balancer that does SSL termination - so as result all incoming traffic to your site is HTTP irrespective what user sees. This causes your code to always try to redirect to HTTPS version and hence infinite loop.
Options to fix:
Usually load balancer that does SSL termination will forward original IP/protocol via custom headers. x-forwarded-proto and x-forwarded-for are common ones to be used for this purpose. You may need to check with network admins if these headers used or some additional configuration is needed
Alternatively you can turn off SSL termination, but it will put additional load on your server.
One can also configure load balancer to talk to server with the same protocol as incoming request.
How to investigate such issue:
Look at http debugger (like Fiddler) to see if you are getting 30x redirects requests in a loop. If no redirects - likely code is wrong.
If you see repeated redirects it likely means site does not see actual request information - could be protocol, path cookies missing.
To continue investigation see what devices are between user and server (CDN, proxies, load balancer,...) - each have good chance to loose some date or transform protocols.
Not that I am against writing nice custom attributes, would it not make sense to perhaps perform the redirect in the web.config and use transformations available for the web.config to change the enabled value below from false to true for production deploys?
<rewrite>
<rules>
<rule name="SSL_ENABLED" enabled="false" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" appendQueryString="true" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
Moving the fix into a separate answer as noted by #AlexeiLevenkov.
public class RemoteRequireHttpsAttribute : RequireHttpsAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentException("Filter Context");
}
if(filterContext.HttpContext != null)
{
if (filterContext.HttpContext.Request.IsSecureConnection)
{
return;
}
var currentUrl = filterContext.HttpContext.Request.Url;
if (currentUrl.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.CurrentCultureIgnoreCase))
{
return;
}
if (string.Equals(filterContext.HttpContext.Request.Headers["X-Forwarded-Proto"], "https", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
if (filterContext.HttpContext.Request.IsLocal)
{
return;
}
var val = ConfigurationManager.AppSettings["RequireSSL"].Trim();
var requireSsl = bool.Parse(val);
if (!requireSsl)
{
return;
}
}
base.OnAuthorization(filterContext);
}
}
and i have updated the ExitHttps attribute as well. This was having the similar issues...
public class ExitHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentException("Filter Context");
}
if (filterContext.HttpContext == null)
{
return;
}
var isSecure = filterContext.HttpContext.Request.IsSecureConnection;
var currentUrl = filterContext.HttpContext.Request.Url;
if (!isSecure && currentUrl.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.CurrentCultureIgnoreCase))
{
isSecure = true;
}
if (!isSecure && string.Equals(filterContext.HttpContext.Request.Headers["X-Forwarded-Proto"], "https", StringComparison.InvariantCultureIgnoreCase))
{
isSecure = true;
}
if (isSecure)
{
//in these cases keep https
// abort if a [RequireHttps] attribute is applied to controller or action
if (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof (RequireHttpsAttribute), true).Length > 0)
{
isSecure = false;
}
if (isSecure && filterContext.ActionDescriptor.GetCustomAttributes(typeof (RequireHttpsAttribute), true).Length > 0)
{
isSecure = false;
}
// abort if a [RetainHttps] attribute is applied to controller or action
if (isSecure && filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof (RetainHttpsAttribute), true).Length > 0)
{
isSecure = false;
}
if (isSecure && filterContext.ActionDescriptor.GetCustomAttributes(typeof (RetainHttpsAttribute), true).Length > 0)
{
isSecure = false;
}
// abort if it's not a GET request - we don't want to be redirecting on a form post
if (isSecure && !String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
isSecure = false;
}
}
if (!isSecure)
{
return;
}
// redirect to HTTP
var url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
I'm using the following code to detect session expiry:
public class SessionActionFilterAttribute : ActionFilterAttribute
{
/// <summary>Called by the ASP.NET MVC framework before the action method executes.</summary>
/// <param name="filterContext">The filter context.</param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// The following code is used for checking if a session has timed out. The default timeout value for ASP.NET is 20mins.
// The timeout value can be overriden in the Web.config file using the sessionState tag's timeout attribute.
// <sessionState timeout="5"></sessionState>
// Check for an existing session.
if (null != filterContext.HttpContext.Session)
{
// Check if we have a new session.
// IsNewSession cannot discern between: is it a new visitor with fresh session, or an existing visitor with expired session.
if (filterContext.HttpContext.Session.IsNewSession)
{
string cookieHeaders = filterContext.HttpContext.Request.Headers["Cookie"];
// Check if session has timed out.
// Does session cookie exist, if so ASP.NET session is expired
if ((null != cookieHeaders) && (cookieHeaders.IndexOf("ASP.NET_SessionId") >= 0))
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
FormsAuthentication.SignOut();
}
// Redirect to login.
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Account" },
{ "action", "Index" },
{ "timeout", "True"}
});
return;
}
}
}
// Else continue with action as usual.
// Session is not expired and function will return false, could be new session, or existing active session
base.OnActionExecuting(filterContext);
}
}
Which works fine up to a point...
When the user is logged in and closes the browser before the session times out (without logging out)...
and then attempts to view the site again and to log back in after the session has timed out it is continually redirecting to the login page, i.e. the above code thinks that the session has expired continuously, but I'm guessing that for some reason the cookie remains as 'expired'.
Is there something I'm missing here?
P.S. I'm using the following in the web.config
<sessionState timeout="1"></sessionState>
Gah.... I added the following just before the redirect and it seems to have fixed the issue.... just a bit more testing to be sure:
if (filterContext.HttpContext.Request.Cookies["ASP.NET_SessionId"] != null)
{
filterContext.HttpContext.Response.Cookies["ASP.NET_SessionId"].Expires = DateTime.Now.AddDays(-1);
}
filterContext.HttpContext.Session.Abandon();
Can I handle forms authentication timeout in Global.asax? Just like the Session_End in global.asax? Please advice.
I'm setting timeout in forms auth in my webconfig with these:
<forms name="formName" loginUrl="Login.aspx" protection="All" path="/" timeout="30"/>
Thanks to all! :)
No you can not because the timeout is encoded on the authentication cookie, and is lives on the browser (not on server side).
You can either make that custom, to also keep on a database the user timeout - but its not so easy, and alternative you can use the Application_AuthenticateRequest on global.asax to check right before the request if the user is not authenticated any more.
One example on how to remove session data if the the user is not authenticate. On global asax.
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
// get the authCookie
HttpCookie authCookie = Context.Request.Cookies[cookieName];
// if is null then the use is not Authendicated
if (null == authCookie && System.Web.HttpContext.Current.Session != null)
{
// now check if you have Session variables that you wish to remove.
if(System.Web.HttpContext.Current.Session["flag"] == "1")
{
// remove your session data
}
}
}
You maybe also check with
if(HttpContext.Current.User == null || HttpContext.Current.User.Identity == null || !HttpContext.Current.User.Identity.IsAuthenticated)
{
// now check if you have Session variables that you wish to remove.
if(Session["flag"] == "1")
{
// remove your session data
}
}
This will be my first question here!
Im having problems with my mvc4 app and random occurring logouts.
i use sessions to store my company id and id of the user.
private void SetSessionData(string UserName)
{
Employee data = (from employee in _db.Employees where employee.Email == UserName select employee).First();
Session.Add("Comp_ID", data.Comp_ID);
Session.Add("Company", data.Company.Name);
Session.Add("User_ID", data.ID);
}
i have set the timeout value to 600 for the session (10 hours) this is even set 2 places to be sure:
[AllowAnonymous]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
//FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); //sørger for at remember me virker!
SetSessionData(model.UserName);
Session.Timeout = 600;
if (model.RememberMe)
{
Response.Cookies.Add(new HttpCookie("CookieUserName", model.UserName) { Expires = DateTime.Now.AddDays(30), Value = model.UserName });
Response.Cookies.Add(new HttpCookie("CookieRememberMe", model.RememberMe.ToString()) { Expires = DateTime.Now.AddDays(30), Value = model.RememberMe.ToString() });//sætter den nye cookie
}
else
{
Response.Cookies.Set(new HttpCookie("CookieUserName") { Expires = DateTime.Now.AddDays(-1) });
Response.Cookies.Set(new HttpCookie("CookieRememberMe") { Expires = DateTime.Now.AddDays(-1) });
}
if (string.IsNullOrEmpty(returnUrl))
{
return RedirectToLocal(returnUrl);
}
return RedirectToAction("Index", "Home");
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "Vi har enten ikke brugernavnet eller koden i kartoteket.");
return View(model);
}
and here in the web.config:
<system.web>
<machineKey validationKey="MyKeyGoesHere" validation="SHA1" decryption="AES" />
<sessionState timeout="600" />
<compilation debug="true" targetFramework="4.5">
<assemblies>
<add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</assemblies>
</compilation>
<httpRuntime targetFramework="4.5" />
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="600" />
</authentication>
My cookies seem to be saved for 10 hours, and my session_id cookie expiration seems to be set to "when the browser closes".
Server side i have set the app pool to recycle at 1am.
Even though all this is set my users still get random logouts form everything between 2 min after login to 1 hour after login.
to counter some of the random half login state problems i have had i included this:
#if(Session == null || Session["User_ID"] == null || !WebSecurity.Initialized){
//Makes sure the session data is cleared and user logged out if session dies.
try
{
if(Session != null) {Session.Clear();}
if (WebSecurity.Initialized){WebSecurity.Logout();}
FormsAuthentication.SignOut();
//dette er til at stoppe cache.
Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(-1));
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore();
}catch{ <p>Error Clearing Login Cache</p>}
}
Im pretty lost by now and hopes a guru out there might know what beginners mistake im making here!
Thanks for ALL response in advance!
Edit:
I also tried this: http://www.windowsitpro.com/article/security-development/create-persistent-id-cookies
(original link from: ASP.NET MVC FormsAuthentication Cookie timeout cannot be increased)
but that just made my app logout every single time i pressed anything after login.
The app is running on windows 2012 server with IIS8.
More adds:
I found out the session_id cookie is still set to when closed in the browser:
cloud.hviidnet.com/image/2X3v2y2e1K1S
The strange thing is its set to 600 min, even when i look in the IIS server:
cloud.hviidnet.com/image/1e3J1g2u3p2M
The solution was to remove all use of "Session." and get all the data from the database instead with WebSecurity.CurrentUserID.
Hope this helps someone else!
Do you only have a single web server? If you have multiple servers load balanced, the session could be getting lost as the user gets routed to different servers between posts, which would explain why it happens at random intervals.
I have the following in my BasePage class which all my ASPX pages derive from:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
ViewStateUserKey = Session.SessionID;
}
I also have a machineKey set in Web.config. I don't think this error is because of a web farm because this happens on my dev machine too.
My host has now upgraded to .NET 3.5 SP1. After this update, everytime I compile with the ViewStateUserKey setting above, I constantly get the "Validation of viewstate MAC failed" error on every postback.
What am I doing wrong here? Is this setting even necessary anymore with the latest framework update?
OK - Im a year late to the conversation - but how is this the correct answer? This applies only in the case of authenticated users and using the ViewStateUserKey as the username is a lot easier to guess than a session id GUID.
BTW if you want to 'fix' the code up top, use the Session ID, however you must set a session variable in order for the session id to stop from changing every time. Ex.
Session["Anything"] = DateTime.Now
ViewStateUserKey = Session.SessionID;
This of course is assuming you are going to use sessions, otherwise you need some other key to use such as the username or any other guid kept in a cookie.
I've searched around quite a bit to find the definitive cause of the issue.
This post from Microsoft really helped explain all the different causes.
http://support.microsoft.com/kb/2915218
Cause 4 is what we have landed on which is an invalid ViewStateUserKeyValue
Setting ViewStateUserKey to Session.SessionID or User.Identity.Name did not work for us.
We intermittently got the validation error due to the following.
When the application pool is reset by IIS, the session is renewed in effect causing the error.
We drop the Session on login to avoid session fixation, also resulting in the error on login.
What finally worked for us was a cookie based solution, which is now provided in VS2012.
public partial class SiteMaster : MasterPage
{
private const string AntiXsrfTokenKey = "__AntiXsrfToken";
private const string AntiXsrfUserNameKey = "__AntiXsrfUserName";
private string _antiXsrfTokenValue;
protected void Page_Init(object sender, EventArgs e)
{
//First, check for the existence of the Anti-XSS cookie
var requestCookie = Request.Cookies[AntiXsrfTokenKey];
Guid requestCookieGuidValue;
//If the CSRF cookie is found, parse the token from the cookie.
//Then, set the global page variable and view state user
//key. The global variable will be used to validate that it matches in the view state form field in the Page.PreLoad
//method.
if (requestCookie != null
&& Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
{
//Set the global token variable so the cookie value can be
//validated against the value in the view state form field in
//the Page.PreLoad method.
_antiXsrfTokenValue = requestCookie.Value;
//Set the view state user key, which will be validated by the
//framework during each request
Page.ViewStateUserKey = _antiXsrfTokenValue;
}
//If the CSRF cookie is not found, then this is a new session.
else
{
//Generate a new Anti-XSRF token
_antiXsrfTokenValue = Guid.NewGuid().ToString("N");
//Set the view state user key, which will be validated by the
//framework during each request
Page.ViewStateUserKey = _antiXsrfTokenValue;
//Create the non-persistent CSRF cookie
var responseCookie = new HttpCookie(AntiXsrfTokenKey)
{
//Set the HttpOnly property to prevent the cookie from
//being accessed by client side script
HttpOnly = true,
//Add the Anti-XSRF token to the cookie value
Value = _antiXsrfTokenValue
};
//If we are using SSL, the cookie should be set to secure to
//prevent it from being sent over HTTP connections
if (FormsAuthentication.RequireSSL &&
Request.IsSecureConnection)
responseCookie.Secure = true;
//Add the CSRF cookie to the response
Response.Cookies.Set(responseCookie);
}
Page.PreLoad += master_Page_PreLoad;
}
protected void master_Page_PreLoad(object sender, EventArgs e)
{
//During the initial page load, add the Anti-XSRF token and user
//name to the ViewState
if (!IsPostBack)
{
//Set Anti-XSRF token
ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
//If a user name is assigned, set the user name
ViewState[AntiXsrfUserNameKey] =
Context.User.Identity.Name ?? String.Empty;
}
//During all subsequent post backs to the page, the token value from
//the cookie should be validated against the token in the view state
//form field. Additionally user name should be compared to the
//authenticated users name
else
{
//Validate the Anti-XSRF token
if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
|| (string)ViewState[AntiXsrfUserNameKey] !=
(Context.User.Identity.Name ?? String.Empty))
{
throw new InvalidOperationException("Validation of
Anti-XSRF token failed.");
}
}
}
}
Source
I fixed it for now by changing the code to:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (User.Identity.IsAuthenticated)
ViewStateUserKey = User.Identity.Name;
}
Can you turn off ViewState MAC encoding with the EnableViewStateMac #Page attribute?
VERY Strange, I too had similar issue for 3 days and now i resolved it.
1. I had enabled forms authentication and had ssl false
<forms defaultUrl="~/" loginUrl="~/Account/Login.aspx" requireSSL="false" timeout="2880" />
but in my httpcookies tag I had requireSSL=true. Since in the Site.Master.cs it uses cookies to set the ViewStateUserKey, it was having issues
hence I was getting the error.
I modified this to false and restarted web app, now its all good.