I have an AccountController
that contains all login, register, two-factor and other operations.
I set a status change in the user profile to enable and disable two-step login.
Everything is done correctly until the user activates it and when he tries to login again, he gets an error.
Error:
ArgumentException: Entity type 'IdentityUserToken' is defined with a 2-part composite key, but 3 values were passed to the 'Find' method.
It is better and clearer in the screenshot 👆
Login method:
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.IsPersistent, lockoutOnFailure: true);
if (result.Succeeded)
{
return LocalRedirect(model.ReturnUrl);
}
if (result.IsLockedOut)
{
ModelState.AddModelError("", "User account locked out!");
return View(model);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction("ToFactorLogin", new { model.Email, model.IsPersistent });
}
else
{
ModelState.AddModelError("", "Invalid login attempt!");
return View(model);
}
}
return View(model);
}
And it does not reach the TwoFactorLogin
action at all and returns an error in the same line
TwoFactorLogin
(HTTP GET
):
[Authorize]
public async Task<IActionResult> ToFactorLogin(string email, bool isPersistent)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return BadRequest();
}
var providers = await _userManager.GetValidTwoFactorProvidersAsync(user);
TwoFactorLoginViewModel model = new TwoFactorLoginViewModel();
if (providers.Contains("Email"))
{
await SendCode(user);
model.Provider = "Email";
model.IsPersistent = isPersistent;
}
else if (providers.Contains("Phone"))
{
string smsCode = await _userManager.GenerateTwoFactorTokenAsync(user, "Phone");
// SmsService smsService = new SmsService();
// smsService.Send(user.PhoneNumber, smsCode);
model.Provider = "Phone";
model.IsPersistent = isPersistent;
}
return View(model);
}
TwoFactorLogin
(HTTP POST
):
[Authorize]
[HttpPost]
public async Task<IActionResult> ToFactorLogin(TwoFactorLoginViewModel model)
{
if (ModelState.IsValid)
{
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
ModelState.AddModelError("", "The email entered is not valid!");
return View();
}
var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.IsPersistent, true);
if (result.Succeeded)
{
return LocalRedirect("~/Index");
}
else if (result.IsLockedOut)
{
ModelState.AddModelError("", "User account locked out!");
return View();
}
else
{
ModelState.AddModelError("", "The entered code is not correct");
return View();
}
}
return View(model);
}
Class IdentityDataBaseContext:
public class IdentityDataBaseContext : IdentityDbContext<User>
{
public IdentityDataBaseContext(DbContextOptions<IdentityDataBaseContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IdentityUser<string>>().ToTable("Users", "identity");
modelBuilder.Entity<IdentityRole<string>>().ToTable("Roles", "identity");
modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("RoleClaims", "identity");
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims", "identity");
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins", "identity");
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRoles", "identity");
modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserTokens", "identity");
modelBuilder.Entity<IdentityUserLogin<string>>().HasKey(p => new { p.LoginProvider, p.ProviderKey });
modelBuilder.Entity<IdentityUserRole<string>>().HasKey(p => new { p.UserId, p.RoleId });
modelBuilder.Entity<IdentityUserToken<string>>().HasKey(p => new { p.UserId, p.LoginProvider });
}
}
Class IdentityConfig:
public static class IdentityConfig
{
public static IServiceCollection AddIdentityService(this IServiceCollection services, IConfiguration configuration)
{
string connection = configuration["ConnectionString:SqlServer"];
services.AddDbContext<IdentityDataBaseContext>(option => option.UseSqlServer(connection));
services.AddIdentity<User, IdentityRole>().AddEntityFrameworkStores<IdentityDataBaseContext>()
.AddDefaultTokenProviders().AddRoles<IdentityRole>().AddErrorDescriber<CustomIdentityError>();
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredUniqueChars = 1;
options.User.RequireUniqueEmail = true;
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
});
return services;
}
}
Thank you for helping me to solve this problem ❤
As the error said, you could try following to config 3-part composite key.
modelBuilder.Entity<IdentityUserToken<string>>().HasKey(p => new { p.UserId, p.LoginProvider,p.Name });