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.