Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constantly Loosing Session State ASP.NET

My site is constantly losing its session state and kicking users off and I can't figure out why.

I have an action filter that tries to check if the UserSession is still present, if not it checks if the user is authenticated and attempts to restore the user session based on the authenticated user id.

If the user isn't authenticated I then redirect them to the login page. I also have some code that checks if it's an ajax request and manually sets the statuscode to 403 so my ajax calls can identify this status and do the redirection within the javascript side.

Here's my Action Filter:

public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            SecuredController baseController = filterContext.Controller as SecuredController;

            //  Check if the session is available
            if (filterContext.HttpContext.Session["UserSession"] == null)
            {

                if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
                {
                    if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
                    {
                        filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
                        filterContext.Result = new JsonResult
                        {
                            Data = new { Error = "Unavailable", Url = "~/Account/Login" },
                            JsonRequestBehavior = JsonRequestBehavior.AllowGet
                        };
                        return;
                    }
                    if (!string.IsNullOrEmpty(HttpContext.Current.Request.RawUrl))
                    {
                        string returnUrl = HttpUtility.UrlEncode(HttpContext.Current.Request.RawUrl);
                        HttpContext.Current.Response.Redirect("~/Account/Login?returnUrl=" + returnUrl);
                    }
                    else
                    {
                        HttpContext.Current.Response.Redirect("~/Account/Login");
                    }
                }

                string userId = filterContext.HttpContext.User.Identity.GetUserId();

                Web.Helpers.Common common = new Helpers.Common();
                UserSession userSession = common.GetUserSession(userId);

                filterContext.HttpContext.Session["UserSession"] = userSession;
            }

            //  Set the Current user to the session variable
            baseController.CurrentUser = (UserSession)filterContext.HttpContext.Session["UserSession"];

            //  Continue executing the relevant action
            base.OnActionExecuting(filterContext);
        }

And here's my Javascript Code:

$.ajax({
            type: method,
            url: rootUrl + serviceUrl,
            async: aSync,
            data: dataParameters,
            cache: false,
            beforeSend: function () {
                if (targetProgressContainer === undefined) {
                    return;
                }
                if ($(targetProgressContainer).length === 0) {
                    console.log('The Progress Container Div "' + targetProgressContainer + ' could not be found!');
                    return;
                }

                $(targetProgressContainer).html($(_progressContainer).html());
            },
            statusCode:{
                403: function (data) {
                    window.top.location.href = sessionEndedUrl;
                }
            },
            success: function (responseData, status, xhr) {
                    successCallback(responseData);
            },
            error: function (request, textStatus, errorThrown) {
                errorCallback(request, textStatus, errorThrown);
            }
        });

Here's my Startup.ConfigureAuth method:

app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider
            // Configure the sign in cookie
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    // Enables the application to validate the security stamp when the user logs in.
                    // This is a security feature which is used when you change a password or add an external login to your account.  
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                },

                SlidingExpiration =true,
                ExpireTimeSpan = TimeSpan.FromDays(30)
            });
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
            app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

And some rules I added to ensure the user url is the full domain

 <rules>
        <rule name="Add www prefix to example.com domain" stopProcessing="true">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTP_HOST}" pattern="^example\.com" />
          </conditions>
          <action type="Redirect" url="http://www.example.com/{R:1}" />
        </rule>
      </rules>

Does anyone have any ideas?

like image 862
Webcognoscere Avatar asked Oct 27 '25 04:10

Webcognoscere


2 Answers

In one of your comments you mentioned that the issue is intermittent - it works fine most of the time but sometimes the user gets "kicked out"? At the risk of asking the obvious - do you use "in-proc" sessions and run your application on the web farm?

The symptoms you described would occur if your app runs behind a load balancer and it uses "in-proc" sessions. In such case the user will be fine as long as the load balancer directs the requests to the same server. If, later on, the load balancer decides to direct one of the requests to another server the user's session information wouldn't be available there and your code will send the redirect to the login page.

UPDATE 1

I focused on the session part but it seems like the problem is with the authentication cookie. In that case the cookie is encrypted/decrypted using keys specified in the <machineKey> part of your web.config. If those settings are different for different servers within the web farm then the auth cookie encrypted on one server cannot be decrypted on a different one causing the "IsAuthenticated" property of the user to be false. Do you have the settings in your web.config as described here: msdn.microsoft.com/en-us/library/eb0zx8fc.aspx?

UPDATE 2 - how to add machine key to your web.config

Following a question in the comments below here's the easiest way to add the machine key to your application (example based on IIS7).

  1. Go to your local IIS select your site and on the fetures pane double-click "Machine Key" icon.

enter image description here

If you don't have your application in the local IIS you can generate the keys on a "dummy" site and copy the configuration section to your app's web.config later.

  1. Click "Generate Keys"

enter image description here

  1. Check the site's web.config

enter image description here

  1. Your keys will look like the example below

enter image description here

You can now simply copy the <machineKey> to your application web.config.

like image 179
Rafal Zajac Avatar answered Oct 30 '25 03:10

Rafal Zajac


We encountered a similar problem in our application, and perhaps this will help you. The authentication token is stored as a cookie, and our application was storing lots of other information in cookies as well, and the names of some of these cookies were dynamically generated. We discovered that different browsers have limits to how many cookies they will store for a site, and will throw away the oldest unmodified cookies. If you haven't refreshed your authentication cookie for a while, it eventually gets to be the oldest one, and gets thrown away. For us, the solution was to move most places where we were using cookies over to local storage, keeping our list of cookies to a well-defined and limited list.

like image 22
Bert Cushman Avatar answered Oct 30 '25 04:10

Bert Cushman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!