Personalization is a hallmark of modern web applications. Whether it is saving a shopping cart, remembering reading preferences, or enabling user-ganerated content, most systems need to know who is on the other side of the HTTP request. This article walkss through adding user accounts to an ASP.NET Core application by leveraging the built-in Identity subsystem.
Authentication vs. Authorization
Before touching any code, clarify the two security pillars:
- Authentication – proving identity, i.e., "Who are you?"
- Authorization – deciding permissions, i.e., "What may you do?"
Authentication always happens first; authorization follows. This guide focuses on authentication; the next article in the series will tackle authorization.
How ASP.NET Core Represents a User
Every request arrives with an HttpContext.User object of type ClaimsPrincipal. Initially the principal is anonymous and carries no claims. After a successful sign-in, the pipeline replaces it with a populated principal that contains claims such as email, name, or custom values like IsVip.
Cookie-Based Workflow for Classic Web Apps
- The browser submits credentials (e.g., email + password) to a login endpoint.
SignInManagerverifies the password hash, builds aClaimsPrincipal, and serializes it into an encrypted cookie.- On every subsequetn request the
AuthenticationMiddlewaredecrypts the cookie, recreates the principal, and assigns it toHttpContext.User.
The AuthenticationMiddleware must be registered after routing but before authorization:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapRazorPages());
API and Mobile Scenarios
Traditional cookie flows do not suit SPAs or mobile clients. Instead, a central identity provider (IdP) issues short-lived bearer tokens (JWT). Clients attach the token to every API call; downstream services validate the signature without contacting the IdP each time. Libraries such as IdentityServer or OpenIddict make it straightforward to stand up an IdP in ASP.NET Core.
Meet ASP.NET Core Identity
Identity is a membership system that handles:
- User storage via EF Core
- Password hashing (PBKDF2 with HMAC-SHA256)
- Account lockout, 2FA, password reset
- External logins (Google, Facebook, etc.)
It purposely omits UI; Microsoft ships a companion package Microsoft.AspNetCore.Identity.UI with 30+ Razor pages you can scaffold and tweak.
Creating a New Project with Identity
Use the template:
dotnet new webapp -au Individual -uld
The generated project contains:
Areas/Identity– Razor pages for login, register, manageApplicationDbContextinheritingIdentityDbContext<IdentityUser>- Pre-configured
AddDefaultIdentityandUseAuthentication
Run dotnet ef database update to create the schema shown below.
Suppose you already have a recipe-management site built in previous chapters. Steps to retrofit Identity:
- Add packages: ```
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.0" />
- Create a custom user entity: ```
public class AppUser : IdentityUser { }
- Derive the existing
AppDbContextfromIdentityDbContext<AppUser>. - Register services: ```
services.AddDefaultIdentity<AppUser>(o => o.SignIn.RequireConfirmedAccount = false)
.AddEntityFrameworkStores<AppDbContext>();
- Add
app.UseAuthentication()in the correct pipeline order. - Create and apply a migration: ```
dotnet ef migrations add IdentitySchema
dotnet ef database update
- Add a login partial to the shared layout: ```
Customizing the Default UI
Scaffold only the pages you need:
dotnet aspnet-codegenerator identity -dc AppDbContext --files "Account.Register;Account.Manage.Index"
After scaffolding you can:
- Edit
Register.cshtmlto remove the external-provider hints. - Delete
Register.cshtml.csand change the@modeldirective to reference the originalRegisterModelfrom the Identity.UI package if you only need view changes.
Storing Extra User Data
Claims are the simplest way to extend user information without touching the database schema. To capture a Full Name at registration:
- Add a
Nameproperty to the scaffoldedRegisterViewModel. - Inside
OnPostAsync: ``` var user = new AppUser { UserName = Input.Email, Email = Input.Email }; var createResult = await _userManager.CreateAsync(user, Input.Password); if (createResult.Succeeded) { await _userManager.AddClaimAsync(user, new Claim("FullName", Input.Name)); await _signInManager.SignInAsync(user, isPersistent: false); return LocalRedirect(returnUrl); } - Display the claim in Razor: ```
@User.FindFirstValue("FullName")
Alternatively, subclass AppUser and add a FullName column when you prefer strongly-typed properties over claims.
Key Takeaways
- Identity gives you a secure, database-backed user store with minimal code.
- Cookie authentication works out of the box for classic web apps; tokens are better for SPAs/mobile.
- The default UI accelerates delivery but is fully replaceable via scaffolding.
- Extend user data either via claims (schema-free) or by extending
IdentityUser(schema change). - Always keep
UseAuthenticationandUseAuthorizationin the correct middleware order.