I have been trying for 2 days to know how to build remember me functionality, but there is nothing clear.
First and foremost, I would like to make sure we agreed of the workflow of this properly as follows.
I need here to allow users to open their profile with no need to signIn again for 1 month, as long as the user doesn't logOut.
I used cookie-based authentication to store some data that I can check every time when user profile opened to make sure that user is authenticated.
-- there is no problem with this stepI use in this step simple code to retrieve data again from the cookie.
-- and here is the problem comes. I can retrieve data from the cookie as long as I'm loggedIn, otherwise, when I stop and re-run the application and redirect to the user profile directly without logIn again I can't read the cookie data although it still exists!!!
Now let's take a look at code
Startup File Cookie Setting
public void ConfigureServices(IServiceCollection services){
.....
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.Cookie.Name = "RememberMecookie"; // cookie name
options.LoginPath = "/Account/LogIn"; // view where the cookie will be issued for the first time
options.ExpireTimeSpan = TimeSpan.FromDays(30); // time for the cookei to last in the browser
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
options.EventsType = typeof(CookieAuthEvent);
});
.....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
}
.....
public class CookieAuthEvent : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
context.Request.HttpContext.Items.Add("ExpiresUTC", context.Properties.ExpiresUtc);
}
}
}
Login ViewModel
public class VMLogin
{
public string UserName { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
}
Controller/Login
[HttpPost]
public async Task<IActionResult> LoginAsync(VMLogin CurrentUserLog, string returnUrl)
{
if (!string.IsNullOrEmpty(CurrentUserLog.UserName) && string.IsNullOrEmpty(CurrentUserLog.Password))
{
return RedirectToAction("Login");
}
if (ModelState.IsValid)
{
var SignInStatus = await signInManager.PasswordSignInAsync
(CurrentUserLog.UserName, CurrentUserLog.Password, CurrentUserLog.RememberMe, false);
AppUser _user = await userManager.FindByNameAsync(CurrentUserLog.UserName);
if (SignInStatus.Succeeded)
{
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) // to prevent login from outside link
{
return Redirect(returnUrl);
}
else
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, CurrentUserLog.UserName),
new Claim(ClaimTypes.Email, _user.Email),
new Claim(ClaimTypes.NameIdentifier, _user.Id.ToString())
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var props = new AuthenticationProperties{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(1)
};
// to register the cookie to the browser
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props).Wait();
return RedirectToAction("UserProfile");
}
}
ModelState.AddModelError(string.Empty, "Invalid Login Attempt");
}
return View(CurrentUserLog);
}
Here is all the problem. I get data from the cookie when I logIn for the first time with the first creation of the cookie as shown in the code above. However, I can't get the same date from the same cookie when I stop debugging and run the app again, and redirect to UserProfile directly without logIn, although the cookie "RememberMecookie" still exists.
Controller/UserProfile
[Authorize]
public async Task<IActionResult> UserProfile()
{
// all lines of code below are working just with the first creation of the cookie with the first login. but if rerun the app again, they all return null if redirect here directly without logIn.
string userId = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
Claim v = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
AppUser _user = await userManager.GetUserAsync(HttpContext.User);
string cookieValueFromReq = Request.Cookies["RememberMecookie"];
// this is for normal login without remember me functionality
//AppUser user = await userManager.GetUserAsync(User);
return View(/*user*/);
}