.NET Identity Framework
Introduction
The .NET Identity Framework is Microsoft's modern membership system for ASP.NET applications. It provides a robust and flexible authentication and authorization system that allows users to register, log in, and manage their accounts. The framework replaces older authentication systems like ASP.NET Membership and Simple Membership, offering improved security features and better integration with modern web development patterns.
Whether you're building a simple website that needs user accounts or a complex application requiring role-based permissions, the .NET Identity Framework provides the tools you need to implement secure user management.
Core Concepts
Before diving into the implementation details, let's understand the key concepts of the .NET Identity Framework:
User Authentication
Authentication is the process of verifying who a user is. The Identity Framework provides mechanisms to:
- Register new users
- Log in existing users
- Implement external login providers (like Google, Facebook, etc.)
- Manage password complexity and security
User Authorization
Authorization determines what an authenticated user can do within your application. The Identity Framework supports:
- Role-based authorization
- Claim-based authorization
- Policy-based authorization
User and Role Management
The framework provides APIs to:
- Create and manage user accounts
- Assign users to roles
- Manage user claims
- Handle account lockouts for security
Getting Started with .NET Identity
Step 1: Setting Up Your Project
To get started with .NET Identity in a new ASP.NET Core project:
- Create a new ASP.NET Core Web Application
- Select the "Web Application" template with "Individual User Accounts" authentication
If you're adding Identity to an existing project, install the required NuGet packages:
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI
Step 2: Configure Identity in Startup.cs
In your Startup.cs
file, you need to configure the Identity services:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 8;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
services.AddControllersWithViews();
services.AddRazorPages();
}
In the Configure
method, ensure you add the authentication middleware:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other middleware configuration...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
Step 3: Creating a Custom User Class
While the default IdentityUser
class is sufficient for many applications, you might want to add custom user properties:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
Then update your DbContext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
And modify your Identity configuration to use your custom user class:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Common Identity Operations
User Registration
Here's a basic controller method to register a new user:
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName,
DateOfBirth = model.DateOfBirth
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
return View(model);
}
User Login
Here's a controller method to handle user login:
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password,
model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
return View(model);
}
Adding Users to Roles
Managing roles is an essential part of authorization. Here's how to add a user to a role:
public async Task<IActionResult> AssignAdminRole(string userId)
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{userId}'.");
}
// Check if the role exists, create it if it doesn't
var roleExists = await _roleManager.RoleExistsAsync("Admin");
if (!roleExists)
{
await _roleManager.CreateAsync(new IdentityRole("Admin"));
}
// Add user to role
await _userManager.AddToRoleAsync(user, "Admin");
return RedirectToAction("UserDetails", new { id = userId });
}
Protecting Routes with Authorization
To restrict access to certain pages or actions, use the [Authorize]
attribute:
[Authorize]
public IActionResult SecureArea()
{
return View();
}
[Authorize(Roles = "Admin")]
public IActionResult AdminPanel()
{
return View();
}
[Authorize(Policy = "RequireAdminRole")]
public IActionResult SuperAdminPanel()
{
return View();
}
Advanced Features
Two-Factor Authentication
Two-factor authentication adds an extra layer of security. Here's how to enable it:
- First, configure the services:
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Other options...
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddTokenProvider<PhoneNumberTokenProvider<ApplicationUser>>("Phone");
- Enable two-factor authentication for a user:
[Authorize]
[HttpPost]
public async Task<IActionResult> EnableTwoFactorAuthentication()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound();
}
await _userManager.SetTwoFactorEnabledAsync(user, true);
await _signInManager.SignOutAsync();
return RedirectToAction("Login");
}
External Authentication Providers
Adding social logins like Google and Facebook:
services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = Configuration["Authentication:Google:ClientId"];
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
})
.AddFacebook(options =>
{
options.AppId = Configuration["Authentication:Facebook:AppId"];
options.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
Claims-Based Authorization
Claims provide more flexibility than roles. Here's how to add claims to a user:
public async Task<IActionResult> AddClaim(string userId, string claimType, string claimValue)
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound();
}
var claim = new Claim(claimType, claimValue);
await _userManager.AddClaimAsync(user, claim);
return RedirectToAction("UserDetails", new { id = userId });
}
And to authorize based on claims:
[Authorize(Policy = "CanManageProducts")]
public IActionResult ManageProducts()
{
return View();
}
The policy would be defined in the Startup.cs:
services.AddAuthorization(options =>
{
options.AddPolicy("CanManageProducts", policy =>
policy.RequireClaim("Permission", "ManageProducts"));
});
Real-World Example: Building a Membership System
Let's create a complete example of a membership system for a small blog where users can register, login, and authors can post articles.
Step 1: Define User and Role Models
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Bio { get; set; }
public virtual ICollection<BlogPost> BlogPosts { get; set; }
}
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime PublishedDate { get; set; }
public string AuthorId { get; set; }
public ApplicationUser Author { get; set; }
}
Step 2: Set Up Database Context
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<BlogPost> BlogPosts { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<BlogPost>()
.HasOne(p => p.Author)
.WithMany(u => u.BlogPosts)
.HasForeignKey(p => p.AuthorId);
}
}
Step 3: Create Seed Data for Roles
public class RoleInitializer
{
public static async Task Initialize(IServiceProvider serviceProvider)
{
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
string[] roleNames = { "Admin", "Author", "Reader" };
foreach (var roleName in roleNames)
{
var roleExist = await roleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
await roleManager.CreateAsync(new IdentityRole(roleName));
}
}
// Create admin user
var adminUser = new ApplicationUser
{
UserName = "[email protected]",
Email = "[email protected]",
FirstName = "Admin",
LastName = "User",
EmailConfirmed = true
};
var userExists = await userManager.FindByEmailAsync(adminUser.Email);
if (userExists == null)
{
var createAdminUser = await userManager.CreateAsync(adminUser, "Password123!");
if (createAdminUser.Succeeded)
{
await userManager.AddToRoleAsync(adminUser, "Admin");
}
}
}
}
Step 4: Create Controllers for Blog Posts
[Authorize(Roles = "Author,Admin")]
public class BlogPostsController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
public BlogPostsController(
ApplicationDbContext context,
UserManager<ApplicationUser> userManager)
{
_context = context;
_userManager = userManager;
}
public async Task<IActionResult> Index()
{
var currentUser = await _userManager.GetUserAsync(User);
if (await _userManager.IsInRoleAsync(currentUser, "Admin"))
{
// Admins can see all posts
return View(await _context.BlogPosts.Include(b => b.Author).ToListAsync());
}
else
{
// Authors can only see their own posts
return View(await _context.BlogPosts
.Where(b => b.AuthorId == currentUser.Id)
.Include(b => b.Author)
.ToListAsync());
}
}
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(BlogPost blogPost)
{
if (ModelState.IsValid)
{
var currentUser = await _userManager.GetUserAsync(User);
blogPost.AuthorId = currentUser.Id;
blogPost.PublishedDate = DateTime.Now;
_context.Add(blogPost);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(blogPost);
}
// Other CRUD operations would follow...
}
Step 5: Implement Account Management
public class AccountController : Controller
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public AccountController(
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager)
{
_signInManager = signInManager;
_userManager = userManager;
_roleManager = roleManager;
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// By default, assign the Reader role
await _userManager.AddToRoleAsync(user, "Reader");
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
}
return View(model);
}
[HttpGet]
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(
model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
if (result.IsLockedOut)
{
return RedirectToAction("Lockout");
}
ModelState.AddModelError("", "Invalid login attempt.");
}
return View(model);
}
[HttpPost]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Index", "Home");
}
// Additional account management methods...
}
Step 6: Implement User Profile Management
[Authorize]
public class ProfileController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
public ProfileController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<IActionResult> Index()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound();
}
var model = new ProfileViewModel
{
Id = user.Id,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
Bio = user.Bio
};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(ProfileViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound();
}
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.Bio = model.Bio;
var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
TempData["StatusMessage"] = "Your profile has been updated";
return RedirectToAction(nameof(Index));
}
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
return View(model);
}
}
Summary
The .NET Identity Framework provides a comprehensive solution for authentication and authorization in ASP.NET applications. In this guide, we've covered:
- Core concepts of authentication and authorization
- Setting up and configuring Identity in your application
- Working with users, roles, and claims
- Implementing common operations like registration and login
- Advanced features like two-factor authentication and external providers
- A real-world example of a complete membership system
By using the .NET Identity Framework, you can ensure that your application has a secure, modern, and flexible authentication system that can grow with your application's needs.
Additional Resources
- Official ASP.NET Core Identity Documentation
- Security and Identity in ASP.NET Core
- Add custom user claims in ASP.NET Core
- Scaffold Identity in ASP.NET Core projects
Exercises
-
Create a new ASP.NET Core project with Identity and customize the user model to include additional fields like address or phone number.
-
Implement role-based authorization in an existing application, with different pages accessible to users in different roles.
-
Set up two-factor authentication using SMS or authenticator apps like Google Authenticator.
-
Integrate external authentication providers like Google, Facebook, or Twitter into your application.
-
Implement a custom password policy that requires passwords to include specific character types or patterns.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)