Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep user login in to system and logout only after user clicks on logout button?

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.

like image 607
Learning-Overthinker-Confused Avatar asked Sep 20 '16 08:09

Learning-Overthinker-Confused


1 Answers

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())

like image 66
trailmax Avatar answered Sep 22 '22 16:09

trailmax