← Tüm yazılara dön

.NET'te Role-Based ve Policy-Based Authorization

.NET'te Role-Based ve Policy-Based Authorization

.NET'te Role-Based ve Policy-Based Authorization

Authorization Neden Karmaşıklaşır?

Basit bir uygulamada yetkilendirme kolaydır:

[Authorize(Roles = "Admin")]
public IActionResult DeleteUser(Guid id) { ... }

Ama gerçek dünya senaryoları hızla karmaşıklaşır:

"Sadece kendi siparişini görebilsin, başkasınınkini değil"
"Manager kendi departmanındaki kullanıcıları yönetebilsin"
"Premium kullanıcı API'yi 1000 kez, normal kullanıcı 100 kez çağırabilsin"
"Fatura sadece oluşturulduğu ayın içinde düzenlenebilsin"
"Admin VE aynı zamanda 2FA aktif olmalı"

[Authorize(Roles = "Admin")] bu senaryoların hiçbirini çözemez. Role-based authorization "kimsin?" sorusuna cevap verir. Policy-based authorization "bunu yapabilir misin?" sorusuna cevap verir. İkisi farklı problemler çözer.


Role-Based Authorization — Temel

Rol Tanımlama

// JWT token üretirken rol claim'i ekleniyor (önceki yazıdan)
var claims = new[]
{
    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
    new Claim(ClaimTypes.Email, user.Email),
    new Claim(ClaimTypes.Role, user.Role) // "Admin", "Manager", "User"
};

Temel Kullanım

[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
    // Sadece Admin
    [HttpDelete("{id}")]
    [Authorize(Roles = "Admin")]
    public async Task<IActionResult> Delete(Guid id) { ... }

    // Admin VEYA Manager
    [HttpGet]
    [Authorize(Roles = "Admin,Manager")]
    public async Task<IActionResult> GetAll() { ... }

    // Herhangi bir authenticated kullanıcı
    [HttpGet("profile")]
    [Authorize]
    public async Task<IActionResult> GetProfile() { ... }

    // Authentication gerektirmez
    [HttpGet("public-info")]
    [AllowAnonymous]
    public IActionResult GetPublicInfo() { ... }
}

Birden Fazla Rol Gerektiren Durum — AND Mantığı

// Hem Admin HEM Auditor olmalı — [Authorize] zincirleme ile AND olur
[Authorize(Roles = "Admin")]
[Authorize(Roles = "Auditor")]
public IActionResult SensitiveOperation() { ... }

// Roles = "Admin,Auditor" ise OR olur — Admin VEYA Auditor yeterli
[Authorize(Roles = "Admin,Auditor")]
public IActionResult LessSensitiveOperation() { ... }

Role-Based'in Sınırları

✅ İyi olduğu yerler:
   - Basit, sabit roller (Admin, User, Manager)
   - Tüm kullanıcılar için aynı kural
   - Az sayıda rol kombinasyonu

❌ Yetersiz kaldığı yerler:
   - "Kendi verisine erişebilsin" (resource ownership)
   - Dinamik kurallar (yaş, abonelik tipi, departman)
   - Birden fazla koşulun birleşimi
   - Zaman bazlı kurallar
   - Hesaplanan yetkiler (örn: kullanım kotası)

Policy-Based Authorization — Temel

Policy, bir veya daha fazla requirement'tan oluşur. Her requirement bir handler tarafından değerlendirilir.

Policy: "MinimumAge18"
   └── Requirement: MinimumAgeRequirement(18)
          └── Handler: MinimumAgeHandler
                 └── Mantık: user.Age >= 18 mi?

Basit Policy — Claim Bazlı

// Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireAdminRole", policy =>
        policy.RequireRole("Admin"));

    options.AddPolicy("RequireVerifiedEmail", policy =>
        policy.RequireClaim("EmailVerified", "true"));

    options.AddPolicy("MinimumAge18", policy =>
        policy.RequireAssertion(context =>
        {
            var dobClaim = context.User.FindFirst("DateOfBirth")?.Value;
            if (dobClaim is null || !DateTime.TryParse(dobClaim, out var dob))
                return false;

            return DateTime.UtcNow.Year - dob.Year >= 18;
        }));
});
[HttpPost("alcohol-products")]
[Authorize(Policy = "MinimumAge18")]
public IActionResult CreateAlcoholProduct() { ... }

Custom Requirement ve Handler

Karmaşık iş kuralları için kendi requirement'ınızı yazın.

Senaryo — Minimum Çalışma Süresi

// Authorization/Requirements/MinimumTenureRequirement.cs
public class MinimumTenureRequirement : IAuthorizationRequirement
{
    public int MinimumMonths { get; }

    public MinimumTenureRequirement(int minimumMonths)
    {
        MinimumMonths = minimumMonths;
    }
}

// Authorization/Handlers/MinimumTenureHandler.cs
public class MinimumTenureHandler
    : AuthorizationHandler<MinimumTenureRequirement>
{
    private readonly AppDbContext _context;

    public MinimumTenureHandler(AppDbContext context)
    {
        _context = context;
    }

    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        MinimumTenureRequirement requirement)
    {
        var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

        if (!Guid.TryParse(userIdClaim, out var userId))
            return; // Requirement karşılanmadı — Fail çağrılmasa da Succeed çağrılmazsa başarısız sayılır

        var employee = await _context.Employees
            .AsNoTracking()
            .FirstOrDefaultAsync(e => e.UserId == userId);

        if (employee is null)
            return;

        var tenureMonths = (DateTime.UtcNow - employee.StartDate).Days / 30;

        if (tenureMonths >= requirement.MinimumMonths)
        {
            context.Succeed(requirement);
        }
    }
}
// Program.cs — kayıt
builder.Services.AddScoped<IAuthorizationHandler, MinimumTenureHandler>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("SeniorEmployeeOnly", policy =>
        policy.Requirements.Add(new MinimumTenureRequirement(12))); // 12 ay
});
[HttpPost("approve-large-expense")]
[Authorize(Policy = "SeniorEmployeeOnly")]
public IActionResult ApproveLargeExpense() { ... }

Resource-Based Authorization — En Yaygın İhtiyaç

"Kendi verisine erişebilsin, başkasınınkine değil" — bu, gerçek projelerde en sık karşılaşılan yetkilendirme problemidir. Role-based bunu çözemez çünkü kural kaynağa (resource) bağlıdır.

Senaryo — Sipariş Sahibi Kontrolü

// Authorization/Requirements/ResourceOwnerRequirement.cs
public class ResourceOwnerRequirement : IAuthorizationRequirement { }

// Authorization/Handlers/OrderOwnerHandler.cs
public class OrderOwnerHandler
    : AuthorizationHandler<ResourceOwnerRequirement, Order>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        ResourceOwnerRequirement requirement,
        Order resource)
    {
        var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

        if (!Guid.TryParse(userIdClaim, out var userId))
            return Task.CompletedTask;

        // Admin her zaman erişebilir
        if (context.User.IsInRole("Admin"))
        {
            context.Succeed(requirement);
            return Task.CompletedTask;
        }

        // Sipariş sahibi mi?
        if (resource.CustomerId == userId)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}
// Program.cs
builder.Services.AddScoped<IAuthorizationHandler, OrderOwnerHandler>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ResourceOwner", policy =>
        policy.Requirements.Add(new ResourceOwnerRequirement()));
});

Controller'da Resource-Based Kullanım

[ApiController]
[Route("api/orders")]
[Authorize]
public class OrdersController : ControllerBase
{
    private readonly AppDbContext _context;
    private readonly IAuthorizationService _authorizationService;

    public OrdersController(
        AppDbContext context,
        IAuthorizationService authorizationService)
    {
        _context = context;
        _authorizationService = authorizationService;
    }

    [HttpGet("{id:guid}")]
    public async Task<IActionResult> GetById(Guid id)
    {
        var order = await _context.Orders
            .AsNoTracking()
            .FirstOrDefaultAsync(o => o.Id == id);

        if (order is null)
            return NotFound();

        // Resource'u handler'a geçir — kullanıcı bu siparişe erişebilir mi?
        var authResult = await _authorizationService.AuthorizeAsync(
            User, order, "ResourceOwner");

        if (!authResult.Succeeded)
            return Forbid();

        return Ok(MapToDto(order));
    }

    [HttpPut("{id:guid}/cancel")]
    public async Task<IActionResult> Cancel(Guid id)
    {
        var order = await _context.Orders.FindAsync(id);

        if (order is null)
            return NotFound();

        var authResult = await _authorizationService.AuthorizeAsync(
            User, order, "ResourceOwner");

        if (!authResult.Succeeded)
            return Forbid();

        order.Cancel("Kullanıcı tarafından iptal edildi.");
        await _context.SaveChangesAsync();

        return NoContent();
    }
}

Çoklu Requirement — Karmaşık Kombinasyonlar

Senaryo — Departman Bazlı Yönetim + Minimum Seviye

// Requirements/DepartmentManagerRequirement.cs
public class DepartmentManagerRequirement : IAuthorizationRequirement
{
    public int MinimumManagementLevel { get; }

    public DepartmentManagerRequirement(int minimumLevel)
    {
        MinimumManagementLevel = minimumLevel;
    }
}

// Handlers/DepartmentManagerHandler.cs
public class DepartmentManagerHandler
    : AuthorizationHandler<DepartmentManagerRequirement, Employee>
{
    private readonly AppDbContext _context;

    public DepartmentManagerHandler(AppDbContext context)
    {
        _context = context;
    }

    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        DepartmentManagerRequirement requirement,
        Employee targetEmployee)
    {
        var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

        if (!Guid.TryParse(userIdClaim, out var userId))
            return;

        var manager = await _context.Employees
            .AsNoTracking()
            .FirstOrDefaultAsync(e => e.UserId == userId);

        if (manager is null)
            return;

        // Aynı departmanda mı?
        var sameDepartment = manager.DepartmentId == targetEmployee.DepartmentId;

        // Yeterli yönetim seviyesinde mi?
        var sufficientLevel = manager.ManagementLevel >= requirement.MinimumManagementLevel;

        if (sameDepartment && sufficientLevel)
        {
            context.Succeed(requirement);
        }
    }
}
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CanManageDepartmentEmployee", policy =>
        policy.Requirements.Add(new DepartmentManagerRequirement(minimumLevel: 2)));
});
[HttpPut("employees/{id:guid}/salary")]
public async Task<IActionResult> UpdateSalary(Guid id, UpdateSalaryRequest request)
{
    var employee = await _context.Employees.FindAsync(id);
    if (employee is null) return NotFound();

    var authResult = await _authorizationService.AuthorizeAsync(
        User, employee, "CanManageDepartmentEmployee");

    if (!authResult.Succeeded)
        return Forbid();

    employee.UpdateSalary(request.NewSalary);
    await _context.SaveChangesAsync();

    return NoContent();
}

Subscription/Plan Bazlı Yetkilendirme — SaaS Senaryosu

// Requirements/FeatureAccessRequirement.cs
public class FeatureAccessRequirement : IAuthorizationRequirement
{
    public string FeatureName { get; }

    public FeatureAccessRequirement(string featureName)
    {
        FeatureName = featureName;
    }
}

// Handlers/FeatureAccessHandler.cs
public class FeatureAccessHandler
    : AuthorizationHandler<FeatureAccessRequirement>
{
    private readonly ISubscriptionService _subscriptionService;

    public FeatureAccessHandler(ISubscriptionService subscriptionService)
    {
        _subscriptionService = subscriptionService;
    }

    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        FeatureAccessRequirement requirement)
    {
        var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

        if (!Guid.TryParse(userIdClaim, out var userId))
            return;

        var hasAccess = await _subscriptionService.HasFeatureAccessAsync(
            userId, requirement.FeatureName);

        if (hasAccess)
        {
            context.Succeed(requirement);
        }
        else
        {
            // Reddetme nedenini context'e ekle — controller'da kullanılabilir
            context.Fail(new AuthorizationFailureReason(
                this, $"'{requirement.FeatureName}' özelliği mevcut planınızda yok."));
        }
    }
}
// Dinamik policy üretimi — her özellik için ayrı policy tanımlamak yerine
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdvancedReporting", policy =>
        policy.Requirements.Add(new FeatureAccessRequirement("advanced-reporting")));

    options.AddPolicy("ApiAccess", policy =>
        policy.Requirements.Add(new FeatureAccessRequirement("api-access")));

    options.AddPolicy("BulkExport", policy =>
        policy.Requirements.Add(new FeatureAccessRequirement("bulk-export")));
});
[HttpGet("reports/advanced")]
[Authorize(Policy = "AdvancedReporting")]
public IActionResult GetAdvancedReport() { ... }

Custom Policy Provider — Tamamen Dinamik Policy'ler

Her özellik için ayrı AddPolicy çağrısı yazmak yerine, attribute'tan policy'yi dinamik üretin:

// Authorization/FeaturePolicyProvider.cs
public class FeaturePolicyProvider : IAuthorizationPolicyProvider
{
    private const string PolicyPrefix = "Feature:";
    private readonly DefaultAuthorizationPolicyProvider _fallbackProvider;

    public FeaturePolicyProvider(IOptions<AuthorizationOptions> options)
    {
        _fallbackProvider = new DefaultAuthorizationPolicyProvider(options);
    }

    public async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
    {
        if (policyName.StartsWith(PolicyPrefix, StringComparison.OrdinalIgnoreCase))
        {
            var featureName = policyName[PolicyPrefix.Length..];

            return new AuthorizationPolicyBuilder()
                .AddRequirements(new FeatureAccessRequirement(featureName))
                .Build();
        }

        return await _fallbackProvider.GetPolicyAsync(policyName);
    }

    public Task<AuthorizationPolicy> GetDefaultPolicyAsync() =>
        _fallbackProvider.GetDefaultPolicyAsync();

    public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() =>
        _fallbackProvider.GetFallbackPolicyAsync();
}
// Program.cs
builder.Services.AddSingleton<IAuthorizationPolicyProvider, FeaturePolicyProvider>();
builder.Services.AddScoped<IAuthorizationHandler, FeatureAccessHandler>();
// Artık her özellik için ayrı policy tanımlamaya gerek yok
[HttpGet("reports/advanced")]
[Authorize(Policy = "Feature:advanced-reporting")]
public IActionResult GetAdvancedReport() { ... }

[HttpPost("data/bulk-export")]
[Authorize(Policy = "Feature:bulk-export")]
public IActionResult BulkExport() { ... }

[HttpGet("api/external")]
[Authorize(Policy = "Feature:api-access")]
public IActionResult ExternalApiAccess() { ... }

Custom Authorization Attribute

Daha okunabilir kod için kendi attribute'unuzu yazın:

// Authorization/Attributes/RequireFeatureAttribute.cs
public class RequireFeatureAttribute : AuthorizeAttribute
{
    public RequireFeatureAttribute(string featureName)
        : base($"Feature:{featureName}")
    {
    }
}
// Kullanım — daha temiz
[HttpGet("reports/advanced")]
[RequireFeature("advanced-reporting")]
public IActionResult GetAdvancedReport() { ... }

Minimal API'lerde Authorization

.NET 8'de Minimal API kullanıyorsanız:

// Program.cs
app.MapGet("/api/orders/{id:guid}", async (
    Guid id,
    AppDbContext context,
    IAuthorizationService authService,
    ClaimsPrincipal user) =>
{
    var order = await context.Orders.FindAsync(id);
    if (order is null) return Results.NotFound();

    var authResult = await authService.AuthorizeAsync(user, order, "ResourceOwner");
    if (!authResult.Succeeded) return Results.Forbid();

    return Results.Ok(order);
})
.RequireAuthorization();

// Policy bazlı
app.MapDelete("/api/users/{id:guid}", async (Guid id, AppDbContext context) =>
{
    // ...
})
.RequireAuthorization("RequireAdminRole");

// Grup bazlı
var adminGroup = app.MapGroup("/api/admin")
    .RequireAuthorization("RequireAdminRole");

adminGroup.MapGet("/users", GetAllUsers);
adminGroup.MapDelete("/users/{id}", DeleteUser);

Authorization Sonucunu Açıklamak

Kullanıcıya neden erişimi reddedildiğini açıklamak deneyimi iyileştirir:

public class AuthorizationFailureMiddleware
{
    private readonly RequestDelegate _next;

    public AuthorizationFailureMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        await _next(context);

        if (context.Response.StatusCode == StatusCodes.Status403Forbidden)
        {
            var authFeature = context.Features.Get<IAuthorizeData>();

            context.Response.ContentType = "application/problem+json";

            await context.Response.WriteAsJsonAsync(new ProblemDetails
            {
                Status = 403,
                Title = "Erişim Reddedildi",
                Detail = "Bu kaynağa erişim izniniz yok."
            });
        }
    }
}

Handler içinde fail nedeni eklemek daha hassas bilgi sağlar:

protected override Task HandleRequirementAsync(
    AuthorizationHandlerContext context,
    FeatureAccessRequirement requirement)
{
    // ...

    if (!hasAccess)
    {
        context.Fail(new AuthorizationFailureReason(
            this,
            $"'{requirement.FeatureName}' özelliğine erişmek için planınızı yükseltmeniz gerekiyor."));
    }

    return Task.CompletedTask;
}
// Controller'da fail reason'ları okuma
var authResult = await _authorizationService.AuthorizeAsync(
    User, null, "Feature:advanced-reporting");

if (!authResult.Succeeded)
{
    var reasons = authResult.Failure?.FailureReasons
        .Select(r => r.Message)
        .ToList() ?? [];

    return StatusCode(403, new ProblemDetails
    {
        Status = 403,
        Title = "Erişim Reddedildi",
        Detail = string.Join(" ", reasons)
    });
}

Multiple Handler — En Az Biri Başarılı Olmalı (OR Mantığı)

Aynı requirement için birden fazla handler tanımlarsanız, herhangi biri başarılı olursa yetkilendirme geçer:

// Requirement: kullanıcı admin VEYA kaynağın sahibi olmalı
public class AdminOrOwnerRequirement : IAuthorizationRequirement { }

public class AdminHandler : AuthorizationHandler<AdminOrOwnerRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        AdminOrOwnerRequirement requirement)
    {
        if (context.User.IsInRole("Admin"))
            context.Succeed(requirement);

        return Task.CompletedTask;
    }
}

public class OwnerHandler : AuthorizationHandler<AdminOrOwnerRequirement, Order>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        AdminOrOwnerRequirement requirement,
        Order resource)
    {
        var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

        if (Guid.TryParse(userIdClaim, out var userId) && resource.CustomerId == userId)
            context.Succeed(requirement);

        return Task.CompletedTask;
    }
}

// İkisi de kaydedilir — biri Succeed derse yetkilendirme geçer
builder.Services.AddScoped<IAuthorizationHandler, AdminHandler>();
builder.Services.AddScoped<IAuthorizationHandler, OwnerHandler>();

Önemli: Handler'larda context.Fail() çağırmayın eğer OR mantığı istiyorsanız — Fail() çağrıldığında diğer handler'lar başarılı olsa bile sonuç başarısız olur. Fail() sadece kesin reddetme gerektiren güvenlik senaryolarında kullanılmalıdır (örneğin hesap askıya alınmışsa).


Test Etmek

// OrderOwnerHandlerTests.cs
public class OrderOwnerHandlerTests
{
    [Fact]
    public async Task Handler_ShouldSucceed_WhenUserIsOwner()
    {
        // Arrange
        var ownerId = Guid.NewGuid();
        var order = new OrderBuilder().WithCustomerId(ownerId).Build();

        var user = new ClaimsPrincipal(new ClaimsIdentity(
        [
            new Claim(ClaimTypes.NameIdentifier, ownerId.ToString())
        ], "TestAuth"));

        var requirement = new ResourceOwnerRequirement();
        var context = new AuthorizationHandlerContext(
            [requirement], user, order);

        var handler = new OrderOwnerHandler();

        // Act
        await handler.HandleAsync(context);

        // Assert
        context.HasSucceeded.Should().BeTrue();
    }

    [Fact]
    public async Task Handler_ShouldSucceed_WhenUserIsAdmin()
    {
        // Arrange
        var order = new OrderBuilder().WithCustomerId(Guid.NewGuid()).Build();

        var user = new ClaimsPrincipal(new ClaimsIdentity(
        [
            new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
            new Claim(ClaimTypes.Role, "Admin")
        ], "TestAuth"));

        var requirement = new ResourceOwnerRequirement();
        var context = new AuthorizationHandlerContext([requirement], user, order);

        var handler = new OrderOwnerHandler();

        // Act
        await handler.HandleAsync(context);

        // Assert
        context.HasSucceeded.Should().BeTrue();
    }

    [Fact]
    public async Task Handler_ShouldFail_WhenUserIsNotOwnerOrAdmin()
    {
        // Arrange
        var order = new OrderBuilder().WithCustomerId(Guid.NewGuid()).Build();

        var user = new ClaimsPrincipal(new ClaimsIdentity(
        [
            new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString())
        ], "TestAuth"));

        var requirement = new ResourceOwnerRequirement();
        var context = new AuthorizationHandlerContext([requirement], user, order);

        var handler = new OrderOwnerHandler();

        // Act
        await handler.HandleAsync(context);

        // Assert
        context.HasSucceeded.Should().BeFalse();
    }
}

Karşılaştırma Tablosu

Kriter Role-Based Policy-Based (Requirement)
Kurulum kolaylığı ✅ Çok kolay ⚠️ Orta — handler yazılmalı
Statik roller ✅ İdeal ⚠️ Gereksiz karmaşıklık
Dinamik kurallar ❌ Yetersiz ✅ İdeal
Resource ownership ❌ Mümkün değil ✅ Destekleniyor
Test edilebilirlik ⚠️ Attribute test zor ✅ Handler izole test edilebilir
Yeniden kullanılabilirlik ⚠️ Sınırlı ✅ Yüksek
Performans ✅ Çok hızlı ⚠️ Handler içinde DB sorgusu olabilir

Denetim Listesi

✅ Basit, statik roller için Role-Based kullanıldı
✅ Dinamik ve karmaşık kurallar için Policy-Based kullanıldı
✅ Resource ownership kontrolü resource-based authorization ile yapıldı
✅ Custom handler'lar DI container'a kaydedildi
✅ OR mantığı gerekiyorsa Fail() çağrılmadı, sadece Succeed() kullanıldı
✅ Kesin reddetme gereken durumlarda Fail() ile açık reddetme yapıldı
✅ Authorization handler'lar birim testlerle kapsandı
✅ Forbidden (403) yanıtları anlamlı mesaj içeriyor
✅ Feature/subscription bazlı erişim merkezi bir serviste yönetiliyor
✅ Minimal API'lerde RequireAuthorization tutarlı kullanıldı
✅ Authorization handler içindeki DB sorguları performans açısından gözden geçirildi

Temel Çıkarımlar

  • Role-based authorization "kimsin" sorusuna, policy-based "yapabilir misin" sorusuna cevap verir
  • Basit, sabit roller için Role-Based yeterlidir — fazla mühendislik yapmayın
  • "Kendi verisine erişebilsin" senaryosu her zaman resource-based authorization gerektirir
  • Custom requirement ve handler'lar karmaşık iş kurallarını test edilebilir hale getirir
  • Birden fazla handler aynı requirement için OR mantığıyla çalışır — Fail() bu mantığı bozar
  • Custom Policy Provider tekrarlayan policy tanımlarını ortadan kaldırır
  • Authorization handler'lar DI container'dan servis enjekte edebilir — veritabanı sorgusu yapılabilir
  • Her authorization kuralı birim testle doğrulanmalıdır — güvenlik kodu test edilmeden production'a girmemeli

Yetkilendirme "kullanıcı giriş yaptı mı" sorusundan çok daha fazlasıdır. Doğru soru her zaman şudur: bu kullanıcı, bu kaynağa, bu anda, bu işlemi yapabilir mi? Role-based ve policy-based authorization birlikte bu sorunun cevabını verir.