Skip to main content

.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:

  1. Create a new ASP.NET Core Web Application
  2. Select the "Web Application" template with "Individual User Accounts" authentication

If you're adding Identity to an existing project, install the required NuGet packages:

bash
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:

csharp
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:

csharp
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:

csharp
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}

Then update your DbContext:

csharp
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}

And modify your Identity configuration to use your custom user class:

csharp
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

Common Identity Operations

User Registration

Here's a basic controller method to register a new user:

csharp
[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:

csharp
[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:

csharp
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:

csharp
[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:

  1. First, configure the services:
csharp
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Other options...
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddTokenProvider<PhoneNumberTokenProvider<ApplicationUser>>("Phone");
  1. Enable two-factor authentication for a user:
csharp
[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:

csharp
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:

csharp
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:

csharp
[Authorize(Policy = "CanManageProducts")]
public IActionResult ManageProducts()
{
return View();
}

The policy would be defined in the Startup.cs:

csharp
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

csharp
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

csharp
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

csharp
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

csharp
[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

csharp
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

csharp
[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:

  1. Core concepts of authentication and authorization
  2. Setting up and configuring Identity in your application
  3. Working with users, roles, and claims
  4. Implementing common operations like registration and login
  5. Advanced features like two-factor authentication and external providers
  6. 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

Exercises

  1. Create a new ASP.NET Core project with Identity and customize the user model to include additional fields like address or phone number.

  2. Implement role-based authorization in an existing application, with different pages accessible to users in different roles.

  3. Set up two-factor authentication using SMS or authenticator apps like Google Authenticator.

  4. Integrate external authentication providers like Google, Facebook, or Twitter into your application.

  5. 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! :)