This is an issue I have been having for weeks now. It specifically seems to happen on my login action in my account controller. It also seems dependent on the machine I am using to browse to the site with, as I only run into the issue on my home machine, not my work machine. I know it is not browser specific as both are using the same version of Chrome. It is also not isolated to a single environment as I will get it debugging locally AND browsing the site itself.
At first I thought it had something to do with the fact that I was using an Async method as I am not as familiar with using them as I am with synchronous methods. I have tried stripping everything out and making the action as well as everything in it completely synchronous and was still getting the same result. I then tried replacing the redirecttoaction/route/locals to just returning a view and it worked. For further confirmation that was the culprit, I had my log in form hit a completely different action that was empty OTHER than a simple redirecttoaction("Index", "Home"), and the app just hung indefinitely. Unfortunately, just hitting a view does not work for my needs as I need it to hit the action itself in many cases.
In terms of its inconsistency, the only way I have been able to consistently reproduce it is to run the app locally and log in. I then close the browser and stop running the app. Then I run the app again>log out>log in, and it hangs.
I have done a good amount of research here on stackoverflow and other sites, and have tried many different recommendations such as changing configuration settings in the startup.cs, reinstalling dlls, and too many other things to count, and I have just had no luck solving this.
Here is the constructor, and the log in get and post action from the account controller
private myContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly IEmailSender _emailSender;
private readonly ISmsSender _smsSender;
private readonly ILogger _logger;
public AccountController(
myContext context,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
RoleManager<IdentityRole> roleManager,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory)
{
_context = context;
_userManager = userManager;
_signInManager = signInManager;
_roleManager = roleManager;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
}
// GET: /Account/Login
[HttpGet]
[AllowAnonymous]
public IActionResult Login(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
ApplicationUser user = _userManager.Users.Where(a => a.Email == model.Email).FirstOrDefault();
if (user != null)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in.");
var RoleCustomer = _userManager.IsInRoleAsync(user, "Customer");
var RoleAdmin = _userManager.IsInRoleAsync(user, "Admin");
if (RoleCustomer.Result)
{
return RedirectToLocal(returnUrl);
}
else if (RoleAdmin.Result)
{
return RedirectToRoute(new { controller = "Home", action = "Admin" });
//return RedirectToLocal("/Home/Index");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
if (user.EmailConfirmed == false)
{
return View("ResendConfirm", new ResendConfirmViewModel() { user = user, returnUrl = returnUrl });
}
if (result.IsLockedOut)
{
_logger.LogWarning(2, "User account locked out.");
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Here is the login form from the login view
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal" role="form">
<h4>Use a local account to log in.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Password" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
<label asp-for="RememberMe">
<input asp-for="RememberMe" />
@Html.DisplayNameFor(m => m.RememberMe)
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Log in</button>
</div>
</div>
<p>
<a asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]">Register as a new user?</a>
</p>
<p>
<a asp-action="ForgotPassword">Forgot your password?</a>
</p>
</form>
Here is the startup class
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<myContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("WebApplication1ContextConnection")));
services.AddIdentityCore<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<myContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
services.AddAuthentication(o =>
{
o.DefaultScheme = IdentityConstants.ApplicationScheme;
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(o => { });
services.AddSession();
services.Configure<IdentityOptions>(options =>
{
// Default Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
options.SignIn.RequireConfirmedEmail = true;
options.SignIn.RequireConfirmedPhoneNumber = false;
options.User.AllowedUserNameCharacters ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
options.User.RequireUniqueEmail = true;
});
services.ConfigureApplicationCookie(options =>
{
//should change this at some point to real access denied. something like Account/AccessDenied
options.AccessDeniedPath = "/Home/index";
options.Cookie.Name = "mycookie";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.LoginPath = "/Account/Login";
// ReturnUrlParameter requires
//using Microsoft.AspNetCore.Authentication.Cookies;
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.SlidingExpiration = true;
});
services.Configure<PasswordHasherOptions>(option =>
{
option.IterationCount = 12000;
});
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseSession();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Thank you so much in advance for any help/guidance you are able to provide. I really appreciate it. If there is any way I can provide more information, please let me know.