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 a
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:
IUserSecurityStampStore
in your store.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())
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.
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
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.
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));
}
}
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();
}