Role-based Authorization — [Authorize(Roles)], Claims, Policy
Muammodan boshlaylik
Do'kon API sida foydalanuvchilar turli:
- Mijoz — katalog ko'radi, buyurtma beradi
- Menejer — mahsulot qo'shadi, narxlarni o'zgartiradi, buyurtmalarni ko'radi
- Admin — hammasi + foydalanuvchilarni boshqarish, o'chirish
Hamma kirgan foydalanuvchi [Authorize] dan o'tadi (61-dars). Har bir action ichida if (email == "admin@dokon.uz") — tartibsizlik va xavf.
Hayotiy o'xshatma
Kasalxona. Hamshira, shifokor va bosh shifokor — hammasi xodim kartasi bilan kiradi. Lekin dori omboriga faqat "farmatsevt" yoki "bosh shifokor" lavozimidagilar, operatsiya xonasiga — "jarroh"lar. Eshik har bir odamni ismidan tanimaydi — lavozimiga qaraydi. Yangi xodim kelsa, eshiklarni qayta sozlash shart emas — unga lavozim beriladi.
Rolni saqlash va tokenga qo'shish
public static class Rollar
{
public const string Admin = "admin";
public const string Menejer = "menejer";
public const string Mijoz = "mijoz";
}
public class Foydalanuvchi
{
public int Id { get; set; }
public string Email { get; set; } = "";
public string ParolXesh { get; set; } = "";
public string Rol { get; set; } = Rollar.Mijoz;
}
Token yaratishda (60-dars):
new Claim(ClaimTypes.Role, f.Rol)
Bir nechta rol bo'lsa — har biri uchun alohida claim:
claims.AddRange(f.Rollar.Select(r => new Claim(ClaimTypes.Role, r)));
[Authorize(Roles = ...)]
[ApiController]
[Route("api/admin/foydalanuvchilar")]
[Authorize(Roles = Rollar.Admin)]
public class AdminFoydalanuvchilarController : ControllerBase { ... }
[HttpPost]
[Authorize(Roles = quot;{Rollar.Admin},{Rollar.Menejer}")] // admin YOKI menejer
public IActionResult MahsulotQosh(MahsulotYaratDto dto) => ...;
- Vergul bilan — YOKI (rollardan biri yetarli)
- Bir nechta atribut — VA (hammasi kerak)
[Authorize(Roles = Rollar.Menejer)]
[Authorize(Roles = "moliya")] // menejer VA moliya
Natija:
- Tokensiz — 401
- Token bor, rol mos emas — 403 Forbidden
Policy orqali — tavsiya etilgan usul
Rol satrlarini o'nlab atributlarga sochmasdan — policy lar:
builder.Services.AddAuthorization(o =>
{
o.AddPolicy("MahsulotBoshqarish", p => p.RequireRole(Rollar.Admin, Rollar.Menejer));
o.AddPolicy("FoydalanuvchiBoshqarish", p => p.RequireRole(Rollar.Admin));
});
[Authorize(Policy = "MahsulotBoshqarish")]
[HttpPut("{id:int}")]
public IActionResult Yangila(int id, MahsulotYangilaDto dto) => ...;
Keyinroq "katalog muharriri" roli qo'shilsa — faqat policy ta'rifi o'zgaradi, controller lar emas.
[!TIP] Atributlarda nima qilish (ruxsat, amal) ni nomlang —
"MahsulotBoshqarish","HisobotKorish"— kim (rol) ni emas. Rollar va ruxsatlar orasidagi bog'lanish bir joyda (policy ta'rifida) bo'ladi. Bu yondashuv katta tizimlarda permission-based avtorizatsiyaga oson o'sadi.
Kod ichida tekshirish
if (User.IsInRole(Rollar.Admin))
{
// admin uchun qo'shimcha maydonlar
}
IAuthorizationService — policy ni kod ichida, resurs bilan tekshirish:
public class BuyurtmaController(IAuthorizationService auth, DokonDbContext db) : ControllerBase
{
[HttpDelete("{id:int}")]
public async Task<IActionResult> Ochir(int id, CancellationToken ct)
{
var b = await db.Buyurtmalar.FindAsync([id], ct);
if (b is null) return NotFound();
var natija = await auth.AuthorizeAsync(User, b, "BuyurtmaEgasiYokiAdmin");
if (!natija.Succeeded) return Forbid();
db.Remove(b);
await db.SaveChangesAsync(ct);
return NoContent();
}
}
o.AddPolicy("BuyurtmaEgasiYokiAdmin", p => p.RequireAssertion(ctx =>
ctx.User.IsInRole(Rollar.Admin) ||
(ctx.Resource is Buyurtma b && b.MijozId.ToString() == ctx.User.FindFirstValue(ClaimTypes.NameIdentifier))));
Rolni o'zgartirish
[HttpPut("{id:int}/rol")]
[Authorize(Policy = "FoydalanuvchiBoshqarish")]
public async Task<IActionResult> RolBer(int id, RolDto dto, CancellationToken ct)
{
if (dto.Rol is not (Rollar.Admin or Rollar.Menejer or Rollar.Mijoz))
return BadRequest("Noma'lum rol");
var f = await db.Foydalanuvchilar.FindAsync([id], ct);
if (f is null) return NotFound();
f.Rol = dto.Rol;
await db.RefreshTokenlar
.Where(r => r.FoydalanuvchiId == id && r.BekorQilingan == null)
.ExecuteUpdateAsync(s => s.SetProperty(r => r.BekorQilingan, DateTime.UtcNow), ct);
await db.SaveChangesAsync(ct);
return NoContent();
}
[!WARNING] Rol tokenning ichida — rol o'zgarsa ham, eski access token eski rol bilan muddati tugaguncha ishlaydi. Shuning uchun rol o'zgarganda refresh tokenlarni bekor qiling (yangi token yangi rol bilan olinadi). Rolni hech qachon ro'yxatdan o'tish DTO sidan qabul qilmang —
{ "rol": "admin" }yuborib admin bo'lib olish klassik zaiflik.
Tez-tez uchraydigan xatolar
ClaimTypes.Role o'rniga "role" claim, MapInboundClaims = false bilan, RoleClaimType sozlanmagan — IsInRole doim false:
o.MapInboundClaims = false;
o.TokenValidationParameters.RoleClaimType = "role";
Rol nomlari katta-kichik harf farqi — "Admin" va "admin"; konstantalar ishlating.
Faqat frontend da yashirish — "O'chirish" tugmasi yashirilgan, lekin API ochiq. Himoya — har doim serverda.
Xulosa
Rol — tokenga ClaimTypes.Role claim i sifatida qo'shiladi. [Authorize(Roles = "a,b")] — rollardan biri (YOKI); bir nechta atribut — VA. Rol mos emas — 403. Policy lar (RequireRole) — rol satrlarini bir joyga yig'adi; atributlarda amalni nomlang.
Kod ichida — User.IsInRole, resursga bog'liq qoidalar — IAuthorizationService.AuthorizeAsync. Rol o'zgarganda — refresh tokenlarni bekor qilish. Rolni foydalanuvchi kiritishidan olmang; himoya har doim serverda.
Keyingi bo'lim
64-dars: AutoMapper — Entity va DTO o'rtasida avtomatik mapping