Authentication with ASP.NET Core Identity
A clinic's reception staff must log in before they can see the day's patient list, and a patient may want an account to view past visits. Writing login code yourself is risky: passwords must be hashed correctly, failed attempts limited, sessions protected and reset links secured. ASP.NET Core Identity is a ready-made membership system that does all of this.
In this lesson you will learn the difference between authentication and authorization, create an app with Identity from a template, understand what it adds, tune password and lockout rules, protect pages, use roles, and see the options for single-page and mobile apps.
Authentication and authorization
Two words that sound alike but mean different things:
- Authentication answers "who are you?" The user proves their identity, for example with an email and password, and the app remembers them, usually with an encrypted cookie.
- Authorization answers "what may you do?" Once the app knows the user, it decides whether they can open the staff dashboard or cancel an appointment.
In ASP.NET Core the signed-in user is a ClaimsPrincipal, available as User in pages and controllers and as a ClaimsPrincipal parameter in minimal APIs. A claim is one fact about the user: their ID, email, name or a role such as Receptionist.
Creating an app with Identity
The quickest start is a template with individual accounts:
dotnet new webapp --auth Individual -o ClinicStaff
cd ClinicStaff
dotnet runThe template stores users in a SQLite database by default (add --use-local-db for SQL Server LocalDB on Windows). It gives you, ready to use:
- An
ApplicationDbContextthat inherits fromIdentityDbContext, with tables such asAspNetUsers,AspNetRolesandAspNetUserRoles, plus a migration that creates them. - Register, login, logout, forgot-password, email confirmation, two-factor and account management pages at URLs such as
/Identity/Account/Login. They come from a Razor Class Library, so the files are not in your project until you scaffold the ones you want to customise. - A
_LoginPartial.cshtmlin the layout that shows "Register" and "Login", or "Hello, name" and "Logout".
The registration in Program.cs looks like this:
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();AddDefaultIdentity registers the user manager, sign-in manager, password hasher, cookie authentication and the default UI. AddEntityFrameworkStores tells Identity to keep users in your EF Core context. With RequireConfirmedAccount = true, new users must confirm their email before logging in; until you configure a real email sender, the template shows a confirmation link on screen for development.
What Identity does for you
- Password hashing: passwords are never stored. Identity stores a salted, deliberately slow hash (PBKDF2), so a stolen database does not reveal passwords.
- Password rules: by default at least 6 characters with an uppercase letter, a lowercase letter, a digit and a non-alphanumeric character.
- Lockout: after 5 failed attempts an account can be locked for 5 minutes, slowing password guessing.
- Security stamps: changing a password invalidates other sessions.
- Two-factor authentication with authenticator apps, and recovery codes.
- Passkeys (new in .NET 10): passwordless sign-in with a fingerprint, face unlock or a security key, based on the WebAuthn standard. The Blazor Web App template includes passkey registration and login.
Tune the rules to your own policy:
builder.Services.Configure<IdentityOptions>(options =>
{
options.Password.RequiredLength = 10;
options.Password.RequireNonAlphanumeric = false; // length matters more than symbols
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.User.RequireUniqueEmail = true;
});Protecting pages
Mark a page model or controller with [Authorize], and anonymous visitors are redirected to the login page, then sent back after logging in:
using Microsoft.AspNetCore.Authorization;
[Authorize]
public class TodayModel(ApplicationDbContext db) : PageModel
{
public string StaffName { get; private set; } = "";
public void OnGet() => StaffName = User.Identity?.Name ?? "";
}For whole areas, use Razor Pages conventions in Program.cs instead of repeating the attribute:
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/Staff");
options.Conventions.AllowAnonymousToPage("/Staff/Help");
});Every page under Pages/Staff now needs a signed-in user, except the help page. [AllowAnonymous] does the same for a single page or action.
Roles
A role groups permissions: Receptionist, Doctor, Admin. Enable roles by adding .AddRoles<IdentityRole>() before AddEntityFrameworkStores, then create roles and assign users with RoleManager and UserManager, for example once at start-up:
builder.Services.AddDefaultIdentity<IdentityUser>(o => o.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var roles = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
foreach (var role in new[] { "Receptionist", "Doctor", "Admin" })
{
if (!await roles.RoleExistsAsync(role))
{
await roles.CreateAsync(new IdentityRole(role));
}
}
}Assign a role with await userManager.AddToRoleAsync(user, "Receptionist"), and restrict a page with [Authorize(Roles = "Receptionist,Admin")] (either role is enough). Role checks in Razor views use User.IsInRole("Doctor"). A user's roles are copied into their cookie at login, so a role change takes effect after they log in again.
APIs, SPAs and mobile apps
Cookies suit server-rendered sites on one domain. For a React front end or an Android app, .NET 8 added Identity API endpoints:
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddAuthorization();
var app = builder.Build();
app.MapGroup("/account").MapIdentityApi<IdentityUser>();This maps JSON endpoints such as POST /account/register and POST /account/login. Login returns either a cookie (?useCookies=true) or an access token and refresh token. These tokens are Identity's own format, not JWTs, and are meant for simple first-party apps. For larger systems, several APIs or third-party clients, use a dedicated identity provider that issues standard JWTs, as described in JWT Bearer Tokens & Authorization. Social logins (Google, Microsoft, Facebook) and company single sign-on with OpenID Connect plug into the same authentication system.
Common mistakes
- Storing passwords yourself. Use Identity or an identity provider; never hash passwords with your own code.
- Leaving lockout off. Change
lockoutOnFailuretotrueon the login page. - Forgetting to send real emails. With
RequireConfirmedAccount, users cannot log in until you implementIEmailSenderwith an email service. - Expecting role changes immediately. The cookie keeps old roles until the user signs in again (or you refresh their sign-in).
- Using Identity API tokens as if they were JWTs. Other services cannot validate them.
Key points
- Authentication proves who the user is; authorization decides what they may do. Users are
ClaimsPrincipalobjects with claims. dotnet new webapp --auth Individualadds Identity with EF Core storage, UI pages and cookie login.- Identity hashes passwords, enforces password and lockout rules, and supports 2FA and, from .NET 10, passkeys.
- Protect pages with
[Authorize]or folder conventions, and group permissions with roles and[Authorize(Roles = ...)]. MapIdentityApiserves JSON login for SPAs and mobile apps; its tokens are not JWTs.
Exercise
Configure Identity for a coaching institute in Lucknow: passwords of at least 12 characters without a forced symbol, unique emails, lockout for 10 minutes after 3 failures, and roles Teacher and Student. Protect every page under Pages/Teachers for the Teacher role, but keep Pages/Teachers/Timetable open to everyone.
Show solution
builder.Services.AddDefaultIdentity<IdentityUser>(o => o.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.Configure<IdentityOptions>(o =>
{
o.Password.RequiredLength = 12;
o.Password.RequireNonAlphanumeric = false;
o.User.RequireUniqueEmail = true;
o.Lockout.MaxFailedAccessAttempts = 3;
o.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
});
builder.Services.AddRazorPages(o =>
{
o.Conventions.AuthorizeFolder("/Teachers", "TeachersOnly");
o.Conventions.AllowAnonymousToPage("/Teachers/Timetable");
});
builder.Services.AddAuthorizationBuilder()
.AddPolicy("TeachersOnly", p => p.RequireRole("Teacher"));AuthorizeFolder accepts a policy name, so the folder requires the Teacher role through the TeachersOnly policy. Create the two roles at start-up with RoleManager, as shown in the lesson, and remember to scaffold the Login page and set lockoutOnFailure: true so the lockout settings take effect.
Quick Quiz
1. What is the difference between authentication and authorization?
The app must first identify the user, then decide on permissions.
2. How does Identity store passwords?
Hashing is one-way; Identity verifies a login by hashing the attempt and comparing.
3. What does options.Conventions.AuthorizeFolder("/Staff") do?
Folder conventions apply authorization without adding attributes to each page.
4. Are the tokens returned by MapIdentityApi login endpoints JWTs?
For standard JWTs that other services can validate, use an identity provider.
Learn ASP.NET Core with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
