14

I am using custom implementation of microsoft asp.net identity because i have custom tables that is why i have given custom implementation of all my methods IUserStore and IUserPasswordStore.

Problem is when user logins then after 10 - 15 minutes login user session gets expired but what i want is unless user logs out i want to keep user login in to the system.

Code:

public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });            
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
            app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
        }
    }

Account Controller:

[Authorize]
    public class AccountController : Controller
    {
        public AccountController()
            : this(new UserManager<UserModel>(new UserStore()))
        {
        }

        public AccountController(UserManager<UserModel> userManager)
        {
            UserManager = userManager;
        }
        public UserManager<UserModel> UserManager { get; private set; }

         [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(string email, string password, bool rememberMe = false, string returnUrl = null)
        {
            if (ModelState.IsValid)
            {
                var user = UserManager.Find(email, password);

                if (user != null)
                {
                    await SignInAsync(user, rememberMe);
                    return RedirectToLocal(returnUrl);
                }
                else
                {
                    ModelState.AddModelError("", "Invalid username or password.");
                }
            }
            return View();
        }

        private async Task SignInAsync(UserModel user, bool isPersistent)
        {
            var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
            identity.AddClaim(new Claim("FullName", user.FirstName + " " + user.LastName));
            identity.AddClaim(new Claim("Email", user.Email));
            identity.AddClaim(new Claim("Role", user.Role));
            AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, ExpiresUtc = DateTime.UtcNow.AddDays(7) }, identity);
        }

 private IAuthenticationManager AuthenticationManager
        {
            get
            {
                return HttpContext.GetOwinContext().Authentication;
            }
        }
    }

Web.config:

<system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules>
      <remove name="FormsAuthentication" />
    </modules>
  </system.webServer>

Now in this below line i have given 7 days of expiry time but still sessions gets expires in 10 - 15 minutes:

  AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, ExpiresUtc = DateTime.UtcNow.AddDays(7) }, identity);

Here in my below question you will find my UserModel,custom UserStore class but for keeping this question small i am not putting that code here:

UserModel and UserStore

Update:I have completely ruled out ApplicationUser class so now below code is useless for me and i think because of this my cookie gets expired i guess(still i am not sure):

 public void ConfigureAuth(IAppBuilder app)
        {
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });            
 }

Note:**The reason behind keeping session active for a long time is because my mvc application is angular driven like Http get call,Http post call so when user session gets expired and i if i try any **get or post call then nothing happens in case of session expires but when i refresh my whole page then user is redirected to login page but till then how user will know what is happening if user doesnot refresh the page.

Community
  • 1
  • 1
I Love Stackoverflow
  • 6,738
  • 20
  • 97
  • 216
  • Can this help you? http://stackoverflow.com/questions/9385716/how-does-the-session-timeout-work-in-iis-7 – Florin-Constantin Ciubotariu Sep 20 '16 at 08:05
  • @CiubotariuFlorin I am using cookie based authentication and not session – I Love Stackoverflow Sep 20 '16 at 08:07
  • Does user login via social networks or using normal login form ? – Fabjan Sep 22 '16 at 15:10
  • @fabjan its a normal login form only – I Love Stackoverflow Sep 22 '16 at 16:48
  • @Fabjan :Its a normal login form only – I Love Stackoverflow Sep 23 '16 at 07:43
  • do you have same result even if you set rememberme and ispersistent to true by default – ergen Sep 23 '16 at 08:30
  • @ergen Yup same result even if i set remember me and ispersistent to true – I Love Stackoverflow Sep 23 '16 at 09:39
  • Can I clarify: the actual problem that you want your user to be logged-in forever (unless logout happens)? or that they are getting logged-out after 10 minutes? – trailmax Sep 26 '16 at 00:32
  • @trailmax : Actual problem is that user logout after 10 -1 5 minutes.i want that user remains login forever unless user explicitly logout from system. – I Love Stackoverflow Sep 26 '16 at 05:13
  • @Learning there are so many things that can kick user out - start from checking if cookies are actually preserved by the client? Are there any modifications to the cookie in transit? Do you seen this issue on all clients or just when developing? Default Identity cookie expiration is sliding 14 days. – trailmax Sep 26 '16 at 09:44
  • 1
    @Learning You are saying you have custom implementation of storage. Does your storage implement `IUserSecurityStampStore`? and if it does, can you please show the code? Users can be logged-out if a `SecurityStamp` for the user is changed. – trailmax Sep 26 '16 at 09:46
  • @trailmax If you can check this question then you will see that i am just implementing IUserStore, IUserPasswordStore.This question contains user store implementation:http://stackoverflow.com/questions/39275597/how-to-give-custom-implementation-of-updateasync-method-of-asp-net-identity – I Love Stackoverflow Sep 26 '16 at 09:53
  • 1
    @Learning yes, I've seen that question - there was nothing about `SecurityStamp`, so I was not sure if that is done somewhere else and not displayed. In this case try removing `OnValidateIdentity = SecurityStampValidator.OnValidateIdentity` from your configuration - this is the bit that uses `SecurityStamp`. It will rule out possibility of getting invalidated cookie because of lack of `SecurityStamp` in the cookie. – trailmax Sep 26 '16 at 09:58
  • @trailmax:Thank you so much for showing your interest but if you see my code in that i am using UserModel and in OnValidateIdentity there is ApplicationUser so i guess that line is not a problem for me.I am not sure but just guessing as because i am very new in asp.net identity and yes my UserModel doesnt implements IUserSecurityStampStore – I Love Stackoverflow Sep 26 '16 at 10:01
  • 1
    @Learning Yes, I see that and that line exactly can be a problem. The sole purpose of `SecurityStampValidator` is to log out users. And there were/are bugs related to that component - just need to make sure you are not running into one of them. – trailmax Sep 26 '16 at 10:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/124195/discussion-between-learning-and-trailmax). – I Love Stackoverflow Sep 26 '16 at 10:14

8 Answers8

5

Your problem is with lack of SecurityStamp. Security stamp is a random string that works as check if password was changed on the server. Security stamp is stored in the cookie and every now-and then checked against the database. If value in the database (store) is different from the value in the cookie - user is asked to login. SecurityStampValidator is doing all the checking and cookie invalidation.

You are using a custom storage for users and that's fine, but your storage does not implement IUserSecurityStampStore and when users login cookie is not getting a value of SecurityStamp. This leads to a malfunction of SecurityStampValidator.

So your options are:

  1. Implement IUserSecurityStampStore in your store.
  2. Remove SecurityStampValidator from your configuration.

I don't like the second option because of the security issue. You want your users to stay logged-in forever - that means the cookie is never invalidated. But when user have 2 browsers, both logged-in. And change password in one of the browsers - second should be logged-out and asked for the password. Without checking for security stamp second browser will not be logged-out and cookie will still be valid. Now imagine that second browser is opened on public computer and user forgot to log out - no way to end that session, even with password change.

To implement IUserSecurityStampStore look on the contract:

/// <summary>
///     Stores a user's security stamp
/// </summary>
/// <typeparam name="TUser"></typeparam>
/// <typeparam name="TKey"></typeparam>
public interface IUserSecurityStampStore<TUser, in TKey> : IUserStore<TUser, TKey> where TUser : class, IUser<TKey>
{
    /// <summary>
    ///     Set the security stamp for the user
    /// </summary>
    /// <param name="user"></param>
    /// <param name="stamp"></param>
    /// <returns></returns>
    Task SetSecurityStampAsync(TUser user, string stamp);

    /// <summary>
    ///     Get the user security stamp
    /// </summary>
    /// <param name="user"></param>
    /// <returns></returns>
    Task<string> GetSecurityStampAsync(TUser user);
}

Basically this adds another column to your users table: SecurityStamp and you need to save there a string. And value for the stamp is any random string. Default Identity implmenetation (around line 734) uses Guid.NewGuid().ToString() - I suggest you do the same.

Your user store will look something like this:

public class UserStore : IUserStore<UserModel>, IUserPasswordStore<UserModel>, IUserSecurityStampStore<TUser>
{
    // your other methods


    public async Task SetSecurityStampAsync(TUser user, string stamp)
    {
        if (user == null)
        {
            throw new ArgumentNullException("user");
        }
        user.SecurityStamp = stamp;
        return Task.FromResult(0);
    }

    Task<string> GetSecurityStampAsync(TUser user)
    {
        if (user == null)
        {
            throw new ArgumentNullException("user");
        }
        return Task.FromResult(user.SecurityStamp);
    }
}

Mind you - you don't need to save user into storage in this operation. UserManager is doing this for you in UpdateSecurityStampAsync - unless you override this method yourself.

Also don't forget to assign a value to SecurityStamp field when create new users. And update all existing users with a value. Something like this will work update MyUsersTable set SecurityStamp = convert(nvarchar(38), NewId())

trailmax
  • 34,305
  • 22
  • 140
  • 234
3

I had the same problem and I was really confused because without any reason user was redirected to login page means that he wasn't authorized. I had changed the timeout to more than 8 hours but nothing was changed. After reading many pages such as Aspnet unexpected logout or frequent-unexpected-user-logoff I found that something is wrong with the machine key and after checking machine key in web.config file I could detect the problem with machine key. By changing the machine key and make it the same with others in Owin section everything is working well.

Community
  • 1
  • 1
  • Can you please post relevant setting to be done in machine config and apart from that i have 1 another concern that i have ruled out Applicationuser and so can this be problem with it? – I Love Stackoverflow Sep 22 '16 at 15:03
3

Have you tried

 ExpireTimeSpan = TimeSpan.FromDays(7);

so this would make your code:

public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });

            ExpireTimeSpan = TimeSpan.FromDays(7);
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
            app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
        }
    }
ShufflerShark
  • 377
  • 1
  • 4
  • 20
2

You should also configure the session timeout at the application pool level in IIS as it is described here: https://technet.microsoft.com/en-us/library/cc771956(v=ws.10).aspx

Trifon
  • 1,081
  • 9
  • 19
  • Thank you so much for your valuable answer but when I will deploy this project on server then this how this setting will work?? – I Love Stackoverflow Sep 20 '16 at 08:34
  • Again AppPool has nothing to do with Cookie based approach, @Learning can you [edit] your question to make this point standout. – Jeremy Thompson Sep 23 '16 at 11:41
  • @JeremyThompson Sorry but i am not sure that how this cookie based approach works with microsoft asp.net identity and owin as because i have just started using it.i was just guessing and honestly i am totally blank on it that why this is happening – I Love Stackoverflow Sep 23 '16 at 12:43
  • @Learning essentially `Sessions` are stored in the process (the 2GB address space on x86's) - after IIS5 Microsoft introduced AppPools. Now what happens when your web app is running is if your `Sessions` are stored **InProc** then the AppPool recycles the process and everyone gets logged out. You can see the settings of IIS by clicking Start > Run > InetMgr – Jeremy Thompson Sep 23 '16 at 12:47
  • So what a lot of companies do is run **Out Of Process** session state storage - here is the [Microsoft KB article on the topic](https://technet.microsoft.com/en-us/library/cc754032(v=ws.10).aspx) - since you are using Cookies which are tiny files that are stored client side on peoples PCs via their browsers - where as Sessions are stored on the server they are two completely different ways of managing state. – Jeremy Thompson Sep 23 '16 at 12:49
  • 1
    I think you'd need to do a bit of reading/experimenting to get up to speed on this but all these people saying set the `Session` stuff up are incorrect because you are using `Cookies` to keep people logged in. Its too big of a topic to handle in comments, perhaps see other ways to achieve your goal: http://stackoverflow.com/questions/10566988/what-is-the-correct-and-safe-secure-way-to-keep-a-user-logged-in-cookies-sessi – Jeremy Thompson Sep 23 '16 at 12:51
  • Using ASP.Net/OWIN/etc don't make much of a difference, these are fundamental WINDOWS OS concepts with IIS (Internet Information Server) I am sharing with you. – Jeremy Thompson Sep 23 '16 at 12:55
  • @JeremyThompson Thank you so much for your kind help and sharing links and yeah you are right that all answers are mostly related to sessions and i am not using session and thank god you are the 1 who have understand this – I Love Stackoverflow Sep 23 '16 at 13:37
  • @JeremyThompson The link you have shared with me is for php and i am using asp.net mvc.At first when i was to decide that what to use for authentication i landed up using microsoft identity and owin but now regretting ober my decision as i find it a bit complex and getting not useful answers too on it – I Love Stackoverflow Sep 23 '16 at 13:40
2

call controller method at specific time interval so it will reset session timeout on evry call. For example if initially you have set your session timeout at 30 min and after 20 min you call this action, it will reset session timeout to again 30 min, by this way your session remains active even reach 30 min after login.

Place your JQuery code in layout

JQuery:

var RefreshSessionInterval;

$(document).ready(function () {        
      clearInterval(RefreshSessionInterval);
      RefreshSessionInterval = setInterval("RefreshSession()", 30000);  // change your interval time as per requirement     
});

function RefreshSession() {
       $.ajax({
            type: "POST",
            url: '@Url.Action("RefreshSession", "YourControllerName")',           
            success: function (data) {               
            },
            error: function () {
            }
       }); 
}

Controller:

Public void RefreshSession()
{
    //your session reset from this line, as i know you don't have to write any code here.
}

public bool LogOut()
{
        LogOff();
        return true;
}

void LogOut()
{       
    Session.Clear();
    Session.Abandon();
    Session.RemoveAll();
    ClearCache();        
}

void ClearCache()
{
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
    Response.Cache.SetExpires(DateTime.Now.AddSeconds(-1));
    Response.Cache.SetNoStore();
    ////FormsAuthentication.SignOut();
}
1

Here's what i did when i coded a user to keep signed in...

Code

public partial class Startup
    {

        public void ConfigureAuth(IAppBuilder app)
        {
            // Enable the application to use a cookie to store information for the signed in user
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login")
            });
// Use a cookie to temporarily store information about a user logging in with a third party login provider
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        }
    }

Account Controller

public class AccountController : Controller
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AccountController"/> class.
        /// </summary>
        public AccountController()
            : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AccountController"/> class.
        /// </summary>
        /// <param name="userManager">The user manager.</param>
        public AccountController(UserManager<ApplicationUser> userManager)
        {
            UserManager = userManager;
        }

        /// <summary>
        /// Gets the user manager.
        /// </summary>
        /// <value>
        /// The user manager.
        /// </value>
        public UserManager<ApplicationUser> UserManager { get; private set; }

        //
        // GET: /Account/Login
        /// <summary>
        /// Logins the specified return URL.
        /// </summary>
        /// <param name="returnUrl">The return URL.</param>
        /// <returns></returns>
        [AllowAnonymous]
        public ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }

        //
        // POST: /Account/Login
        /// <summary>
        /// Logins the specified model.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <param name="returnUrl">The return URL.</param>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                var user = await UserManager.FindAsync(model.UserName, model.Password);
                if (user != null)
                {
                    await SignInAsync(user, model.RememberMe);
                    return RedirectToLocal(returnUrl);
                }
                else
                {
                    ModelState.AddModelError("", "Invalid username or password.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
        {
            AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
            var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
            AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
        }

OR.. You can also configure the session timeout for a user at the application pool level in IIS.

Yaman Ahlawat
  • 477
  • 6
  • 17
0

Session lifetime (how long until the session disappears) and authentication lifetime (how long until the user has to login again) are two separate and distinct timeframes.

If authentication lifetime is longer than session timeframe this means that a session will start with the user already authenticated (i.e. the user will not need to login to initiate a session).

If authentication lifetime is shorter than session timeframe this means a user will be forced to login before their session expires. I'm not certain whether the session is "refreshed" when the user reauthenticates (as a guess... probably).

Just setting very long expirations for session and authentication might not be a piratical production ready solution (i.e. there's lots of ways for sessions to "disappear").

Why do you care if the user's session disappears and then a new one is started (without the user having to login)? Without a bit more information about what you're intending I can't really understand the core of your question.

user2845090
  • 147
  • 3
  • 14
0

Examine the settings of the forms element within the authentication element of your web.config file.

Note the default values for the two applicable settings.

  1. timeout (default is 30 minutes)
  2. slidingExpiration (True or False / default varines with .NET Framework version)

For your situation, you will probably want a timeout duration much higher than 30 minutes and a slidingExpiration value of True.

JohnH
  • 1,920
  • 4
  • 25
  • 32