2

I have an MVC4 single application page. In the log-in page there are 3 fields: user, password and "remember me" checkbox. The C# login code is this:

   if (WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
   {
       FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
       return Json(new { success = true, redirect = returnUrl });
   }
   else
   {
       ModelState.AddModelError("", "The user name or password provided is incorrect.");
   }

I want to do this:

  • If a user logs in with "remember me" true - The cookie will stay until the user logs off.

  • If the user logs in with "remember me" false - The cookie will expire after 3 hours.

In web.config I have this code:

<authentication mode="Forms">
      <forms loginUrl="~/" timeout="180" cookieless="UseCookies" slidingExpiration="false" />
</authentication>
<sessionState timeout="180"  />

The problem is when the user doesn't touch the page for a short time (10-30 mins usually), and then the user tries to do something in the page - there is an error

"Authorization has been denied for this request."

(Although sessionStation is more than 30 minutes!)

After the user refresh the page - if the cookie hasn't expired yet - everything works fine. But of course I don't want to make the user refresh the page every 15 minutes or so, it's a single-page-application.

So, I tried to change slidingExpiration to "true" and create a ping (with ajax) every 17 minutes and it really stopped the session expiration and the annoying message - but the cookie didn't expired after 3 hours (I logged in with remember me 'false')!

What can I do?

TamarG
  • 3,522
  • 12
  • 44
  • 74

3 Answers3

3

Right click your application pool from IIS management console and look for "Idle time-out(minutes)".

You can adjust the setting to 0 (zero) which effectively disables the timeout so that the application pool will never shut down due to being idle.

ApiFox
  • 123
  • 1
  • 5
  • OK, it'll solve the problem with the 20-minutes-expiration but can I make a different between remember me true (that will stay "forever") and remember me false (stay for 3 hours only)? – TamarG Jan 29 '15 at 09:46
  • You could put the logic in cookies, and process that in javascript. – ApiFox Feb 02 '15 at 08:56
  • Since I use " [Authorize(Roles = "Role1")]" attributes - I'd like to use the original cookies and not to handle another one – TamarG Feb 02 '15 at 09:24
2

I would double check your IIS settings. See what your App Pool Idle Timeout value is set to. By default it's 20 minutes. When the App Pool goes idle (no users accessing the app pool) for twenty minutes, you will loose session state (All data stored in the session will be cleared).

With more users this problem would work itself out, but presuming you are the only person testing, increasing this value to something greater than 180 minutes will prevent the timeout, or you could set the value to zero to disable the app pool idle timeout altogether.

See this answer for information on checking your app pool timeout in IIS Express... https://stackoverflow.com/a/10222419/386856

Do note that a dead app pool can take several seconds to re-spawn. It may be beneficial to increase this value anyways. This will prevent users from having an extremely slow experience if they happen to be the person that's unlucky enough to have to wait for the app pool to restart.

Update

To allow for a change in timeout for users who don't click remember me, you can create a custom attribute or you could modify the FormsAuthentication timeout via C#. Here are good references on setting the timeout via code. https://msdn.microsoft.com/en-us/library/system.web.configuration.formsauthenticationconfiguration.timeout%28v=vs.110%29.aspx and https://msdn.microsoft.com/en-us/library/system.web.configuration.formsauthenticationconfiguration(v=vs.110).aspx If you want the timeout to expire right at 3 hours, even if the user has activity be sure slidingExpiration is false in your web config. If you want the timeout to expire 3 hours after the user's last activity be sure slidingExpiration is true. So before you set the cookie try something like the following (Be sure to check the OpenWebConfiguration path):

    System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("/aspnetTest");
    AuthenticationSection authenticationSection = (AuthenticationSection)configuration.GetSection("system.web/authentication");
    FormsAuthenticationConfiguration formsAuthentication = authenticationSection.Forms;
    formsAuthentication.Timeout = System.TimeSpan.FromHours(3);
    formsAuthentication.SlidingExpiration = true;
Community
  • 1
  • 1
puddinman13
  • 1,408
  • 4
  • 18
  • 35
  • OK, it'll solve the problem with the 20-minutes-expiration without needing to ping, but can I make a different between remember me true (that will stay "forever") and remember me false (stay for 3 hours only)? – TamarG Jan 29 '15 at 11:05
  • You will probably need a code implementation for that. I would say raise your App Pool Idle timeout, as it's probably a good idea. Then use an Action Filter attribute to check if the Remember Me false user has been logged on for longer than 3 hours. If so kick them off, if not, let them continue. One way to do the check would be to store the login time in the cookie and then in the attribute verify three hours hasn't exceeded. – puddinman13 Jan 29 '15 at 14:43
  • 1
    Added another alternative to the answer, see above. – puddinman13 Jan 29 '15 at 14:57
  • Is there a way to make SlidingExpiration true for "rememberMe = true" and false for "rememberMe = false"? It'll solve all my problems... – TamarG Feb 01 '15 at 07:09
  • 1
    I will lookin to it @TamarG – puddinman13 Feb 01 '15 at 07:53
  • @TamarG, there is actually a SlidingExpiration property on the formsAuthentication object. See answer above. You should be able to use an if statement to determine whether it will be true or false. Cheers :-) – puddinman13 Feb 01 '15 at 08:10
  • I've already tried this - it didn't help... On "FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);" - FormsAuthentication.SlidingExpiration is still false. – TamarG Feb 01 '15 at 08:13
  • Sorry if I wasn't clear but dont use the FormsAuthentication object @TamarG but use the FormsAuthenticationConfiguration object as shown in my answer above – puddinman13 Feb 01 '15 at 08:15
  • This is what I did but after that I have the line "FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);" to set the cookie (look at my original code). Is there another way to set the cookie? – TamarG Feb 01 '15 at 08:27
0

I solved this by just creating my own encrypted cookie which will persist the user's session if present. In an action filter attribute, check if the user's session is expired, check for this cookie. If the cookie exists, verify the information and reestablish the session. If the user doesn't have a session, doesn't have a cookie, or the encrypted credentials are incorrect, redirect the user to the login page. Since everything is handled in the code, there is no guess work on the server settings.

On login with remember me checked:

            if (RememberMe ?? false)
            {
                var authCookie = new HttpCookie(Config.AuthorizationCookie);

                authCookie.Values.Add("ID", Crypto.EncryptStringAES(UserSession.Current.Account.ID.ToString(), Config.SharedSecret));

                authCookie.Expires = DateTime.Now.AddDays(30);
                AuthorizationCookie = authCookie;
            }
            Response.AppendCookie(AuthorizationCookie);

On page visit without session (done inside an attribute attached to necessary controllers and actions):

        _account = UserSession.Current.Account;

        // check if there is currently an account in the session
        if(_account == null)
        {
            // check the user authorization cookie for a user id
            HttpCookie authCookie = HttpContext.Current.Request.Cookies[Config.AuthorizationCookie] ?? new HttpCookie(Config.AuthorizationCookie);
            if (authCookie.HasKeys && !String.IsNullOrEmpty(authCookie["ID"]))
            {
                // decrypt the user id for database lookup
                _userID = Crypto.DecryptStringAES(authCookie.Values["ID"], Config.SharedSecret);
            }
        }

Re-establish the session if necessary (done inside a database connection)

    // called within a database connection's using statement
    protected override void Query(DatabaseContext db)
    {
        // check the database for the user account
        if(_account == null && !String.IsNullOrEmpty(_userID))
        {
            int tempID;
            int? id;
            id = int.TryParse(_userID, out tempID) ? tempID : (int?)null;

            if(id.HasValue)
            {
                _sessionRestored = true;
                _account = db.User.Find(id);

                if(_account != null)
                {
                    _account.LastLogon = DateTime.UtcNow;
                    db.SaveChanges();
                }
            }
        }
    }

Finally restore the session:

        if (_account != null)
        {
            // set the current account
            UserSession.Current.Account = _account;

            if(_sessionRestored)
            {
                UserSession.Current.Refresh();
            }
        }

Your code may look a bit different but that is the crux of how I did it.

Chris Peterson
  • 713
  • 7
  • 13
  • The only problem I can see with that is if you have some data stored in the session it will be cleared after 20 minutes. Doesn't sound like that's the ops case, but did want to consider the possibility. – puddinman13 Jan 28 '15 at 22:07
  • @NathanRenico good point, I'm not trying to persist anything across sessions at the moment on this project. Storing that information in a database or with a cookie seems a good way to go. I think this is probably what the OP wants https://technet.microsoft.com/en-us/library/cc725820%28v=ws.10%29.aspx – Chris Peterson Jan 28 '15 at 22:42
  • where and when do you "Finally restore the session"? – TamarG Feb 01 '15 at 05:28
  • I didn't understand what to do exactly, and visual studio can't find "Config" and "UserSession" for example – TamarG Feb 01 '15 at 07:54
  • Config and UserSession are just classes I created to run my project. It sounds like you want something out of the box, what I'm suggesting is more of a custom solution. – Chris Peterson Feb 01 '15 at 17:34