Authorization
Phân quyền truy cập trong ASP.NET Core
Mô hình Authorization
ASP.NET Core authorization được xây dựng từ 4 thành phần chính, xếp theo thứ bậc. Mỗi tầng phụ thuộc vào tầng bên dưới để ra quyết định cuối cùng.
Khi endpoint có [Authorize(Policy = "X")],
hệ thống tìm policy X, lấy danh sách requirements, chạy tất cả handlers cho từng requirement. Nếu
MỌI requirement đều có ít nhất 1 handler gọi Succeed()
VÀ không handler nào gọi Fail(),
authorization thành công.
Role-Based Authorization
Cách đơn giản nhất — kiểm tra user có thuộc role nhất định không.
Toàn quyền quản lý hệ thống, user và cấu hình
Tạo, chỉnh sửa và xuất bản nội dung
Chỉ xem nội dung, không có quyền chỉnh sửa
// Chỉ Admin mới truy cập được [Authorize(Roles = "Admin")] public IActionResult ManageUsers() => View(); // Admin HOẶC Editor (dấu phẩy = OR) [Authorize(Roles = "Admin,Editor")] public IActionResult EditPost() => View(); // Phải vừa là Admin VÀ Editor (nhiều attribute = AND) [Authorize(Roles = "Admin")] [Authorize(Roles = "Editor")] public IActionResult SpecialAction() => View();
Decision Flow
[Authorize(Roles)] trên cùng một action, chúng hoạt động
theo logic AND — user phải có TẤT CẢ các roles. Còn
roles cách nhau bởi dấu phẩy trong CÙNG attribute hoạt động theo logic
OR.
Claims-Based Authorization
Linh hoạt hơn role-based. Thay vì hỏi "user có role X không?", ta hỏi "user có claim Y với value Z không?".
Interactive Claims Tree
ClaimsPrincipal
ClaimsIdentity: "Cookies"
ClaimsIdentity: "Bearer"
// Kiểm tra claim trực tiếp trong controller public IActionResult Profile() { var email = User.FindFirstValue(ClaimTypes.Email); var department = User.FindFirstValue("Department"); var roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value); if (User.HasClaim(c => c.Type == "Department" && c.Value == "Engineering")) { // User thuộc phòng Engineering } return View(); }
Policy-Based Authorization
Cách mạnh mẽ và linh hoạt nhất. Policy là tập hợp các điều kiện (requirements) mà user phải thỏa mãn.
Đăng ký Policies
// Program.cs — Đăng ký policies builder.Services.AddAuthorization(options => { // Policy đơn giản: yêu cầu claim options.AddPolicy("EmailVerified", policy => policy.RequireClaim("email_verified", "true")); // Policy kết hợp nhiều điều kiện options.AddPolicy("CanEditPost", policy => { policy.RequireAuthenticatedUser(); // Phải đăng nhập policy.RequireRole("Editor", "Admin"); // Phải có role policy.RequireClaim("email_verified", "true"); // Email đã xác thực }); // Policy với custom requirement options.AddPolicy("MinimumAge", policy => policy.Requirements.Add(new MinimumAgeRequirement(18))); });
Sử dụng Policies
[Authorize(Policy = "CanEditPost")] public IActionResult Edit(int id) => View(); // Hoặc trong Minimal API app.MapDelete("/posts/{id}", DeletePost) .RequireAuthorization("CanEditPost");
Decision Flow
- 1. RequireAuthenticatedUser Handler checks → Succeed ✓
- 2. RequireRole("Editor") Handler checks → Succeed ✓
- 3. RequireClaim("email_verified") Handler checks → Fail ✗
Custom Requirements & Handlers
Khi built-in policies không đủ, bạn tự tạo requirement và handler riêng.
Custom Requirement
// Requirement: User phải đủ tuổi tối thiểu public class MinimumAgeRequirement : IAuthorizationRequirement { public int MinimumAge { get; } public MinimumAgeRequirement(int age) => MinimumAge = age; }
Custom Handler
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement> { protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, MinimumAgeRequirement requirement) { var dobClaim = context.User.FindFirst(c => c.Type == "DateOfBirth"); if (dobClaim == null) return Task.CompletedTask; // Không gọi Succeed cũng không Fail // → Handler khác có thể xử lý var dob = DateTime.Parse(dobClaim.Value); var age = DateTime.Today.Year - dob.Year; if (age >= requirement.MinimumAge) { context.Succeed(requirement); // ✓ Thỏa mãn! } return Task.CompletedTask; // Lưu ý: KHÔNG gọi Fail() ở đây // Nếu không Succeed, requirement vẫn chưa thỏa mãn // nhưng handler khác vẫn có cơ hội Succeed } }
Đăng ký Handler
// Program.cs — Đăng ký handler builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
Logic đánh giá Handler
Hiểu rõ cách hệ thống đánh giá kôt quả từ các handlers là chìa khóa để viết authorization logic chính xác.
context.Succeed(requirement)
→ Requirement PASSED
context.Fail()
→ Toàn bộ authorization FAILED
→ Authorization thành công → 200 OK
→ 403 Forbidden
context.Fail() trừ khi bạn muốn CHẮC CHẮN từ chối, bất kể handler
khác nói gì. Thường thì chỉ cần không gọi Succeed() là đủ
— điều này cho phép các handler khác có cơ hội xử lý.
Resource-Based Authorization
Đôi khi bạn cần kiểm tra quyền dựa trên resource cụ thể (ví dụ: "user này có phải tác giả của bài viết này không?"). Không thể dùng attribute vì resource chỉ có sau khi load từ database.
Resource Handler
public class PostAuthorizationHandler : AuthorizationHandler<EditRequirement, Post> { protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, EditRequirement requirement, Post post) { // Tác giả luôn được edit bài của mình if (post.AuthorId == context.User.FindFirstValue(ClaimTypes.NameIdentifier)) { context.Succeed(requirement); } return Task.CompletedTask; } }
Sử dụng trong Controller
// Trong controller — inject IAuthorizationService public async Task<IActionResult> Edit(int id) { var post = await _db.Posts.FindAsync(id); var result = await _authService.AuthorizeAsync(User, post, new EditRequirement()); if (!result.Succeeded) return Forbid(); // 403 return View(post); }
[Authorize] attribute vì resource
chưa tồn tại tại thời điểm filter chạy. Thay vào đó, inject IAuthorizationService
và gọi AuthorizeAsync() trực tiếp trong action method sau khi đã load resource từ
database.