2

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.

  1. 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 step

  2. I 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*/);
    }
MOHAMED ABUELATTA
  • 305
  • 2
  • 5
  • 15
  • Does this answer your question? [Implement "Remember me" in ASP.NET CORE 3.1 MVC](https://stackoverflow.com/questions/66028454/implement-remember-me-in-asp-net-core-3-1-mvc) – Roar S. Jun 21 '21 at 21:13
  • 1
    @RoarS. I already searching for this for 2 days long and I know that post. I edited my code to show I have tried it but still not related to my problem. I add UseAuthentication class above. But this is to reset the cookie timespan and not related to my problem. – MOHAMED ABUELATTA Jun 21 '21 at 21:25
  • It's a little bit unclear to me what you're trying to do here. You have an auth cookie called `RememberMecookie`, and you're trying to read a cookie called `RememberMeBlogAcademy` which is not set in the code in your question. – Roar S. Jun 21 '21 at 22:17
  • 1
    @RoarS. sorry it's a typo I have edited it. thank you so much for your time and help – MOHAMED ABUELATTA Jun 21 '21 at 22:34

2 Answers2

1

Thanks For all guys who spent time checking out my question. I finally found the problem. This code is really great and it can be a good reference for remembering me functionality using cookie-based Authentication. And there is no problem with the code itself.

The problem was with my Startup file

It was like this

services.AddMvc(config =>
{
    var policy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();
    config.Filters.Add(new AuthorizeFilter(policy));
});

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => {
    options.Cookie.Name = "RememberMeBlogAcademy";
    options.LoginPath = "/Account/LogIn";
    //options.LogoutPath = "/Home/Index";
    //options.AccessDeniedPath = "AccessDenied";
    options.ExpireTimeSpan = TimeSpan.FromDays(30);
    options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
    //options.Cookie.Expiration = TimeSpan.FromDays(5);
    options.EventsType = typeof(CookieAuthEvent);
});
//services.AddScoped<CookieAuthEvent>();

 services.AddControllersWithViews();

The problem was using MVC and AddControllersWithViews together. I didn't know that would make a problem.

However, It should be like this -- using AddControllersWithViews

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => {
            options.Cookie.Name = "RememberMeBlogAcademy";
            options.LoginPath = "/Account/LogIn";
            //options.LogoutPath = "/Home/Index";
            //options.AccessDeniedPath = "AccessDenied";
            options.ExpireTimeSpan = TimeSpan.FromDays(30);
            options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
            //options.Cookie.Expiration = TimeSpan.FromDays(5);
            options.EventsType = typeof(CookieAuthEvent);
});
services.AddScoped<CookieAuthEvent>();

services.AddControllersWithViews(config =>
{
     var policy = new AuthorizationPolicyBuilder()
         .RequireAuthenticatedUser()
         .Build();
     config.Filters.Add(new AuthorizeFilter(policy));
});

Moreover, You don't need to retrieve data from the cookie as shown in Controller/UserProfile above.

Also, when I made debugging to check out the code I tested logout to make sure I really retrieve users data from the cookie not from UserManager and It really works well.

Here is the additional code of logOut

[Authorize]
public async Task<IActionResult> Logout()
{
    await signInManager.SignOutAsync();
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return RedirectToAction("Index", "Home");
}
MOHAMED ABUELATTA
  • 305
  • 2
  • 5
  • 15
  • I am having the same issue as yours here. I have cookie in place in browser. Tried to use your suggestions here, but once I reload any page in my app, I am redirected to Login in page without possibility to access any other pages before logging in again. Do you have some example you have been using? I am wondering if I have something wrong elsewhere... – 10101 Apr 15 '22 at 12:03
0

Get the cookie value

var cookie = Request.Cookies["cookieName"];