Error: Invalid login attempt. with a password you know is right — ASP.NET Core Identity refused the
sign-in because the account was never confirmed, and the project template requires confirmation.
The Blazor Web App template registers Identity with options.SignIn.RequireConfirmedAccount = true.
A user created by a seeder, a script or an admin page has EmailConfirmed = false unless you set
it, so PasswordSignInAsync returns NotAllowed — and the template's login page reports that
with the same text it uses for a wrong password. If your accounts are created by you and there is no email
pipeline, set RequireConfirmedAccount = false; if you keep the requirement, seed the users with
EmailConfirmed = true.
The error
The message and the branch that produces it, in the template's Login.razor:
result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded) { /* ... */ }
else if (result.RequiresTwoFactor) { /* ... */ }
else if (result.IsLockedOut) { /* ... */ }
else
{
errorMessage = "Error: Invalid login attempt.";
}
Why it happens
SignInResult has four outcomes the page checks for and one it does not: IsNotAllowed,
which Identity returns when the password is correct but the account may not sign in yet — unconfirmed email,
unconfirmed phone, or a confirmed-account policy. The final else catches both a wrong password and a
correct-but-not-allowed one, so the screen cannot tell you which happened.
And nobody can confirm anything: the template ships IdentityNoOpEmailSender, which sends nothing. A
seeded staff account in an app with no email pipeline is locked out forever by a default it never chose.
The fix
// src/ClinicLive/Program.cs
builder.Services.AddIdentityCore<ApplicationUser>(options =>
{
// Staff accounts are created by an admin/seeder, not self-service signup —
// no email pipeline exists, so confirmed accounts would lock everyone out.
options.SignIn.RequireConfirmedAccount = false;
options.Stores.SchemaVersion = IdentitySchemaVersions.Version3;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
Two alternatives. If real users register themselves and you do send confirmation mail, keep the requirement and
have the seeder mark its own accounts — ClinicLive's does this as well, belt and braces:
new ApplicationUser { UserName = email, Email = email, EmailConfirmed = true }. And in either case,
make the page honest: check result.IsNotAllowed before the final else and say
"this account is not confirmed" instead of blaming the password.
Where it bit us
Season one, Part 5 (tag part-05 in
the repo; the setting is switched off in the
SQLite-to-PostgreSQL swap commit, before the seeder existed). Left alone, the template default would have
bounced every seeded staff login, and the AI — which had read the same Program.cs — never flagged
it until asked, in plain words, "why can't I log in?", at which point it named the setting instantly. The lesson the part kept: AI is superb at answering the
question you ask and unreliable at volunteering the one you should have asked. Read the template's
Program.cs top to bottom before you accept it.
Frequently asked
- Why does ASP.NET Core Identity say Invalid login attempt when the password is correct?
- The template's login page shows that message for every failed SignInResult except two-factor and lockout, including NotAllowed. NotAllowed is returned when RequireConfirmedAccount is on and the user's email has not been confirmed, which is always the case for accounts created by a seeder in an app without an email sender.
- Should I turn RequireConfirmedAccount off in production?
- Only if accounts are created by an administrator or a seeder rather than by public self-registration. If users sign up themselves, keep the requirement, send real confirmation email, and set EmailConfirmed to true on any account your own code creates.
- How do I tell an unconfirmed account from a wrong password?
- Check result.IsNotAllowed after PasswordSignInAsync. It is true when the credentials were right but sign-in is not permitted yet, so you can show a specific message instead of the generic Invalid login attempt.
More decoded errors in the Fixes category; the app this came from starts at From Prompt to Production, Part 1.