Welcome to Module 6 of our ASP.NET Core Complete Course: Beginner to Advanced Guide for Modern Web Development. In this module, we dive deep into Authentication & Authorization, critical components for securing ASP.NET Core applications. This comprehensive, SEO-friendly guide covers Identity in ASP.NET Core, Authentication Methods (Cookies, JWT, OAuth, OpenID), Role-based & Policy-based Authorization, and Securing APIs with JWT.
Packed with practical examples, detailed explanations, real-world scenarios, pros and cons, alternatives, best practices, and tips for security, performance, and error handling, this blog post will equip you with the skills to build secure, scalable web applications. Whether you're a beginner or an advanced developer, this module will help you master authentication and authorization in ASP.NET Core. Let’s get started!
Table of Contents
Introduction to Authentication & Authorization
Identity in ASP.NET Core
What is ASP.NET Core Identity?
Pros, Cons, and Alternatives
Setting Up ASP.NET Core Identity
Example: Implementing ASP.NET Core Identity
Authentication Methods in ASP.NET Core
Cookie Authentication
JWT Authentication
OAuth and OpenID Connect
Pros, Cons, and Alternatives for Each Method
Example: Implementing Cookie and JWT Authentication
Role-based & Policy-based Authorization
What is Role-based Authorization?
What is Policy-based Authorization?
Pros, Cons, and Alternatives
Example: Implementing Role-based and Policy-based Authorization
Securing APIs with JWT
Why Use JWT for APIs?
Pros, Cons, and Alternatives
Example: Building a Secure API with JWT
Best Practices for Authentication & Authorization
Security Best Practices
Performance Best Practices
Error Handling Best Practices
Real-Life Example: Building a Secure E-Commerce Application
Conclusion
FAQs
Introduction to Authentication & Authorization
Authentication and Authorization are the cornerstones of secure web applications. Authentication verifies who a user is, while authorization determines what a user can do. In ASP.NET Core, these mechanisms are robust, flexible, and integrated into the framework, supporting various authentication methods and authorization strategies.
In Module 6, we’ll explore:
ASP.NET Core Identity: A framework for managing user authentication and authorization.
Authentication Methods: Cookie-based, JWT, OAuth, and OpenID Connect.
Role-based & Policy-based Authorization: Controlling access based on roles and custom policies.
Securing APIs with JWT: Protecting RESTful APIs with token-based authentication.
This blog post includes detailed explanations, practical examples, pros and cons, alternatives, and best practices for security, performance, and error handling. We’ll also walk through a real-life e-commerce application to demonstrate these concepts in action.
Why is this important?
Authentication: Ensures only legitimate users access your application.
Authorization: Restricts access to resources based on user permissions.
Security: Protects sensitive data and prevents unauthorized access.
Scalability: Supports modern authentication protocols for APIs and single-page applications (SPAs).
User Experience: Provides seamless login and access control.
Let’s dive into each topic with detailed explanations and real-world examples.
Identity in ASP.NET Core
What is ASP.NET Core Identity?
ASP.NET Core Identity is a membership system that handles user authentication, registration, password management, and role-based authorization. It provides a complete framework for managing users, roles, claims, and tokens, with built-in support for database storage using Entity Framework Core.
Key Features
User Management: Register, login, logout, and manage user profiles.
Password Management: Hashing, password reset, and validation.
Role Management: Assign roles to users for authorization.
Claims-based Identity: Support for custom user data (claims).
External Authentication: Integration with OAuth providers like Google, Microsoft, and Facebook.
Pros, Cons, and Alternatives
Pros
Comprehensive: Handles most authentication and authorization needs out of the box.
Customizable: Supports custom user stores, claims, and policies.
Secure: Uses secure password hashing and supports two-factor authentication (2FA).
Database Integration: Works seamlessly with EF Core for persistent storage.
Community Support: Widely used with extensive documentation.
Cons
Complexity: Can be overkill for simple applications.
Learning Curve: Requires understanding of Identity’s components and configuration.
Performance Overhead: Additional database queries for user management.
Tight Coupling: Heavily tied to EF Core, which may not suit all projects.
Alternatives
Custom Authentication: Build a lightweight authentication system using cookies or JWT.
IdentityServer4/Duende IdentityServer: For advanced OAuth/OpenID Connect scenarios.
Auth0 or Okta: Cloud-based identity providers for simpler integration.
Setting Up ASP.NET Core Identity
To use ASP.NET Core Identity:
Install the Microsoft.AspNetCore.Identity.EntityFrameworkCore NuGet package.
Configure Identity services and the database context.
Set up authentication middleware.
Example: Implementing ASP.NET Core Identity
Let’s create an MVC application with Identity for user registration and login.
Step 1: Create a New MVC Project
dotnet new mvc -o MyIdentityApp
cd MyIdentityApp
Step 2: Install NuGet Packages
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
Step 3: Create the Identity ContextCreate Data/AppDbContext.cs:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace MyIdentityApp.Data
{
public class AppDbContext : IdentityDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
}
}
Step 4: Configure IdentityUpdate Program.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MyIdentityApp.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
builder.Services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Update appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyIdentityAppDb;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Step 5: Create Migrations
dotnet ef migrations add InitialCreate
dotnet ef database update
Step 6: Add Authentication UIInstall the Identity UI package:
dotnet add package Microsoft.AspNetCore.Identity.UI
Update Program.cs to include default UI:
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
Step 7: Create a Login ViewUpdate Views/Home/Index.cshtml:
<h1>Welcome to My Identity App</h1>
@if (User.Identity.IsAuthenticated)
{
<p>Hello, @User.Identity.Name!</p>
<a asp-area="Identity" asp-page="/Account/Logout" class="btn btn-danger">Logout</a>
}
else
{
<a asp-area="Identity" asp-page="/Account/Login" class="btn btn-primary">Login</a>
<a asp-area="Identity" asp-page="/Account/Register" class="btn btn-secondary">Register</a>
}
Step 8: Run the ApplicationRun the application with dotnet run. Navigate to / to see the login/register links. Register a new user and log in to test the Identity system.
Output:
After registering and logging in, the home page displays: “Hello, username!” with a logout button.
This example demonstrates setting up ASP.NET Core Identity for user management with a SQL Server database.
Authentication Methods in ASP.NET Core
ASP.NET Core supports multiple authentication methods, including Cookie Authentication, JWT Authentication, OAuth, and OpenID Connect. Each method suits different scenarios, from traditional web apps to APIs and third-party integrations.
Cookie Authentication
Cookie Authentication stores user credentials in a secure cookie, suitable for server-rendered applications like MVC or Razor Pages.
Pros
Simple: Easy to set up for web applications.
Secure: Uses encrypted cookies with built-in protections.
Stateful: Maintains user sessions on the server.
Cons
Not Ideal for APIs: Cookies are less suitable for stateless APIs.
Scalability Issues: Session management can be challenging in distributed systems.
Cross-Origin Limitations: CORS issues with cross-domain requests.
Alternatives
JWT for stateless authentication.
Session-based authentication with Redis for distributed systems.
JWT Authentication
JSON Web Token (JWT) authentication uses tokens to authenticate users, ideal for APIs and single-page applications (SPAs).
Pros
Stateless: No server-side session storage.
Scalable: Works well in distributed systems.
Cross-Platform: Suitable for mobile and web clients.
Cons
Token Management: Requires secure storage and handling.
Revocation Challenges: Tokens are valid until expiration.
Larger Payload: Tokens can increase request size.
Alternatives
OAuth for third-party authentication.
Refresh tokens for long-lived sessions.
OAuth and OpenID Connect
OAuth is an authorization protocol for granting access to resources, while OpenID Connect builds on OAuth to provide authentication. They enable integration with external providers like Google, Microsoft, or Facebook.
Pros
Third-Party Integration: Simplifies login with external providers.
Secure: Leverages industry-standard protocols.
Scalable: Supports distributed systems and SPAs.
Cons
Complexity: Requires understanding of OAuth/OpenID flows.
Dependency on Providers: Relies on external services.
Configuration Overhead: Setup can be intricate.
Alternatives
Custom JWT-based authentication.
ASP.NET Core Identity for simpler scenarios.
Example: Implementing Cookie and JWT Authentication
Let’s implement both Cookie and JWT authentication in an ASP.NET Core application.
Cookie Authentication Example
Step 1: Update Program.cs
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Account/AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Step 2: Create AccountController.csCreate Controllers/AccountController.cs:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using System.Threading.Tasks;
namespace MyIdentityApp.Controllers
{
public class AccountController : Controller
{
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(string username, string password)
{
if (username == "admin" && password == "password123") // Simplified for demo
{
var claims = new[]
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, "Admin")
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(principal);
return RedirectToAction("Index", "Home");
}
ViewData["Error"] = "Invalid credentials";
return View();
}
public IActionResult AccessDenied()
{
return View();
}
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Index", "Home");
}
}
}
Step 3: Create ViewsCreate Views/Account/Login.cshtml:
<h1>Login</h1>
<form asp-action="Login" method="post">
<div class="form-group">
<label for="username">Username</label>
<input name="username" class="form-control" />
</div>
<div class="form-group">
<label for="password">Password</label>
<input name="password" type="password" class="form-control" />
</div>
<p class="text-danger">@ViewData["Error"]</p>
<button type="submit" class="btn btn-primary">Login</button>
</form>
Create Views/Account/AccessDenied.cshtml:
<h1>Access Denied</h1>
<p>You do not have permission to access this page.</p>
<a asp-controller="Home" asp-action="Index" class="btn btn-secondary">Back to Home</a>
Step 4: Test Cookie AuthenticationRun the application and navigate to /Account/Login. Log in with username: admin and password: password123. The home page will reflect the authenticated user.
JWT Authentication Example
Step 1: Install JWT Package
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Step 2: Update Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "MyIssuer",
ValidAudience = "MyAudience",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secure-key-32-chars-long-at-least"))
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Step 3: Create a Token ControllerCreate Controllers/TokenController.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace MyIdentityApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TokenController : ControllerBase
{
[HttpPost]
public IActionResult GenerateToken(string username, string password)
{
if (username == "admin" && password == "password123") // Simplified for demo
{
var claims = new[]
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, "Admin")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secure-key-32-chars-long-at-least"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "MyIssuer",
audience: "MyAudience",
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}
return BadRequest("Invalid credentials");
}
}
}
Step 4: Create a Protected APICreate Controllers/ProductsController.cs:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace MyIdentityApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok(new[] { "Laptop", "Smartphone" });
}
}
}
Step 5: Test JWT AuthenticationUse a tool like Postman:
POST to /api/token with username: admin and password: password123 to get a JWT.
Use the token in the Authorization header (Bearer <token>) to access /api/products.
Output for /api/products:
["Laptop", "Smartphone"]
This example demonstrates both Cookie and JWT authentication, showcasing their use in web and API scenarios.
Role-based & Policy-based Authorization
Authorization determines what an authenticated user can do. ASP.NET Core supports Role-based and Policy-based Authorization for fine-grained access control.
What is Role-based Authorization?
Role-based Authorization restricts access based on user roles (e.g., Admin, User). It’s simple and effective for many applications.
Pros
Simple: Easy to implement and understand.
Scalable: Works well for applications with clear role hierarchies.
Built-in Support: Integrated with ASP.NET Core Identity.
Cons
Limited Flexibility: Can’t handle complex authorization logic.
Role Explosion: May lead to many roles in large systems.
Alternatives
Policy-based Authorization for more granular control.
Claims-based Authorization for custom attributes.
What is Policy-based Authorization?
Policy-based Authorization uses policies to define complex access rules, combining roles, claims, and custom logic.
Pros
Flexible: Supports custom requirements (e.g., age, location).
Reusable: Policies can be applied across multiple actions.
Extensible: Integrates with custom logic and services.
Cons
Complexity: Requires more setup than role-based authorization.
Maintenance: Policies can become complex in large systems.
Alternatives
Role-based Authorization for simpler scenarios.
Custom middleware for specific use cases.
Example: Implementing Role-based and Policy-based Authorization
Let’s extend the Cookie Authentication example with role-based and policy-based authorization.
Step 1: Update AccountController.csAdd role assignment:
[HttpPost]
public async Task<IActionResult> Login(string username, string password)
{
if (username == "admin" && password == "password123")
{
var claims = new[]
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, "Admin"),
new Claim("Department", "IT") // For policy-based authorization
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(principal);
return RedirectToAction("Index", "Home");
}
ViewData["Error"] = "Invalid credentials";
return View();
}
Step 2: Define a PolicyUpdate Program.cs:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ITDepartment", policy =>
policy.RequireClaim("Department", "IT"));
});
Step 3: Create a Protected ControllerCreate Controllers/AdminController.cs:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace MyIdentityApp.Controllers
{
[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize(Policy = "ITDepartment")]
public IActionResult ITDashboard()
{
return View();
}
}
}
Step 4: Create ViewsCreate Views/Admin/Index.cshtml:
<h1>Admin Dashboard</h1>
<p>Welcome to the Admin Dashboard!</p>
<a asp-action="ITDashboard" class="btn btn-primary">IT Dashboard</a>
Create Views/Admin/ITDashboard.cshtml:
<h1>IT Department Dashboard</h1>
<p>Accessible only to IT department members.</p>
Step 5: Test AuthorizationRun the application and log in with username: admin and password: password123. Navigate to:
/Admin (accessible due to Admin role).
/Admin/ITDashboard (accessible due to IT department claim).
This example demonstrates role-based ([Authorize(Roles = "Admin")]) and policy-based ([Authorize(Policy = "ITDepartment")]) authorization.
Securing APIs with JWT
Why Use JWT for APIs?
JWT (JSON Web Token) is a compact, self-contained token format for secure API authentication. It’s widely used in RESTful APIs and SPAs due to its stateless nature and cross-platform compatibility.
Pros
Stateless: No server-side session storage.
Interoperable: Works with any client (web, mobile, etc.).
Secure: Signed tokens prevent tampering.
Cons
Token Management: Clients must securely store tokens.
Revocation: Tokens are valid until expiration.
Size: Larger tokens increase request size.
Alternatives
OAuth 2.0 for third-party authentication.
API keys for simpler scenarios (less secure).
Example: Building a Secure API with JWT
Let’s enhance the JWT example with a more robust API.
Step 1: Update TokenController.csAdd user validation and refresh token logic:
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace MyIdentityApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TokenController : ControllerBase
{
private readonly IConfiguration _configuration;
public TokenController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost]
public IActionResult GenerateToken([FromBody] LoginModel model)
{
if (model.Username == "admin" && model.Password == "password123") // Replace with real user validation
{
var claims = new[]
{
new Claim(ClaimTypes.Name, model.Username),
new Claim(ClaimTypes.Role, "Admin"),
new Claim("Department", "IT")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
refreshToken = Guid.NewGuid().ToString() // Simplified refresh token
});
}
return BadRequest(new { error = "Invalid credentials" });
}
}
public class LoginModel
{
public string Username { get; set; }
public string Password { get; set; }
}
}
Step 2: Update appsettings.json
{
"Jwt": {
"Key": "your-secure-key-32-chars-long-at-least",
"Issuer": "MyIssuer",
"Audience": "MyAudience"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Step 3: Update ProductsController.csAdd policy-based authorization:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace MyIdentityApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = "ITDepartment")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
try
{
var products = new[] { "Laptop", "Smartphone" };
return Ok(products);
}
catch (Exception ex)
{
return StatusCode(500, new { error = "An error occurred while retrieving products", details = ex.Message });
}
}
}
}
Step 4: Test the APIUse Postman to:
POST to /api/token with { "username": "admin", "password": "password123" }.
Use the returned token to access /api/products.
This example demonstrates securing an API with JWT and policy-based authorization, including basic error handling.
Best Practices for Authentication & Authorization
Security Best Practices
Use Strong Passwords: Enforce complex password policies (e.g., minimum length, special characters).
Enable HTTPS: Always use HTTPS to encrypt data in transit.
Secure Tokens: Store JWTs securely (e.g., HttpOnly cookies for web apps).
Implement 2FA: Use two-factor authentication for sensitive applications.
Validate Tokens: Use strong keys and validate issuer/audience for JWT.
Limit Token Lifespan: Use short-lived access tokens with refresh tokens.
Sanitize Inputs: Prevent injection attacks in login forms.
Performance Best Practices
Optimize Database Queries: Minimize Identity-related queries in EF Core.
Cache Tokens: Use in-memory caching for token validation (e.g., Redis).
Stateless Authentication: Prefer JWT for APIs to reduce server load.
Asynchronous Operations: Use async methods for authentication/authorization tasks.
Error Handling Best Practices
Handle Authentication Failures: Return meaningful error messages (e.g., “Invalid credentials”) without exposing sensitive details.
Log Errors: Use logging (e.g., ILogger) to capture authentication failures.
Graceful Degradation: Redirect to custom error pages (e.g., AccessDenied).
Try-Catch Blocks: Wrap critical operations (e.g., token generation, API calls) in try-catch blocks.
Real-Life Example: Building a Secure E-Commerce Application
Let’s build a secure e-commerce application with ASP.NET Core Identity, Cookie Authentication, JWT for APIs, and role-based/policy-based authorization.
Step 1: Set Up the ProjectCreate an MVC project with Identity and EF Core (as shown in the Identity section).
Step 2: Define ModelsCreate Models/Product.cs:
namespace MyIdentityApp.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
}
Update Data/AppDbContext.cs:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using MyIdentityApp.Models;
namespace MyIdentityApp.Data
{
public class AppDbContext : IdentityDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Product> Products { get; set; }
}
}
Step 3: Configure Authentication and AuthorizationUpdate Program.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using MyIdentityApp.Data;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
builder.Services.AddAuthentication()
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ManagersOnly", policy =>
policy.RequireRole("Manager").RequireClaim("Department", "Sales"));
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
Update appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyIdentityAppDb;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Jwt": {
"Key": "your-secure-key-32-chars-long-at-least",
"Issuer": "MyIssuer",
"Audience": "MyAudience"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Step 4: Create a Products ControllerCreate Controllers/ProductsController.cs:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyIdentityApp.Data;
using MyIdentityApp.Models;
using System.Threading.Tasks;
namespace MyIdentityApp.Controllers
{
public class ProductsController : Controller
{
private readonly AppDbContext _context;
public ProductsController(AppDbContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
return View(await _context.Products.ToListAsync());
}
[Authorize(Policy = "ManagersOnly")]
public IActionResult Manage()
{
return View();
}
}
}
Create Views/Products/Index.cshtml:
@model List<MyIdentityApp.Models.Product>
<h1>Products</h1>
<ul>
@foreach (var product in Model)
{
<li>@product.Name - $@product.Price (@product.Category)</li>
}
</ul>
@if (User.IsInRole("Manager"))
{
<a asp-action="Manage" class="btn btn-primary">Manage Products</a>
}
Create Views/Products/Manage.cshtml:
<h1>Manage Products</h1>
<p>Accessible only to Sales Managers.</p>
Step 5: Create an API ControllerCreate Controllers/Api/ProductsApiController.cs:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyIdentityApp.Data;
using MyIdentityApp.Models;
using System.Threading.Tasks;
namespace MyIdentityApp.Controllers.Api
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = "ManagersOnly")]
public class ProductsApiController : ControllerBase
{
private readonly AppDbContext _context;
public ProductsApiController(AppDbContext context)
{
_context = context;
}
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
var products = await _context.Products.ToListAsync();
return Ok(products);
}
catch (Exception ex)
{
return StatusCode(500, new { error = "Failed to retrieve products", details = ex.Message });
}
}
}
}
Step 6: Seed Initial DataAdd a seeding method in Program.cs:
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
if (!await roleManager.RoleExistsAsync("Manager"))
{
await roleManager.CreateAsync(new IdentityRole("Manager"));
}
var user = new IdentityUser { UserName = "manager@example.com", Email = "manager@example.com" };
await userManager.CreateAsync(user, "Password123!");
await userManager.AddToRoleAsync(user, "Manager");
await userManager.AddClaimAsync(user, new Claim("Department", "Sales"));
dbContext.Products.AddRange(
new Product { Name = "Laptop", Price = 999.99m, Category = "Electronics" },
new Product { Name = "Smartphone", Price = 499.99m, Category = "Electronics" });
await dbContext.SaveChangesAsync();
}
Step 7: Test the Application
Navigate to / and log in as manager@example.com with Password123!.
Access /Products to view products.
Access /Products/Manage (requires Manager role and Sales department claim).
Use Postman to get a JWT from /api/token and access /api/ProductsApi (requires ManagersOnly policy).
Real-Life Scenario: This e-commerce application simulates a store where:
Customers can view products (public access).
Managers can manage products (role-based and policy-based authorization).
A mobile app can access the product API using JWT for secure data retrieval.
Conclusion
In Module 6 of our ASP.NET Core Complete Course, we explored Authentication & Authorization in ASP.NET Core, covering:
ASP.NET Core Identity for user management.
Authentication Methods (Cookies, JWT, OAuth/OpenID).
Role-based & Policy-based Authorization for access control.
Securing APIs with JWT for stateless authentication.
Through practical examples and a real-life e-commerce application, you’ve learned how to implement secure authentication and authorization, with best practices for security, performance, and error handling. In the next module, we’ll dive into advanced topics like SignalR, Blazor, and performance optimization. Stay tuned!
FAQs
Q1: What is the difference between authentication and authorization?
Authentication verifies a user’s identity, while authorization determines what they can access.
Q2: When should I use Cookie vs. JWT authentication?
Use Cookie authentication for server-rendered apps (MVC, Razor Pages) and JWT for APIs and SPAs.
Q3: What is the benefit of policy-based authorization?
Policy-based authorization allows complex, reusable access rules based on roles, claims, or custom logic.
Q4: How do I secure JWT tokens?
Use strong signing keys, short expiration times, HTTPS, and secure client storage (e.g., HttpOnly cookies).
Q5: Can I combine Identity with JWT?
Yes, ASP.NET Core Identity can generate JWTs for APIs while supporting cookie-based authentication for web apps.
No comments:
Post a Comment
Thanks for your valuable comment...........
Md. Mominul Islam