Mert Özen Her Satırda Daha İleri
Güvenlik API Güvenliği (OWASP API Top 10) — #05

API4 Unrestricted Resource Consumption: Sayfa Boyutu, Sorgu Karmaşıklığı, Timeout'lar, Kota ve Maliyetli Dış Çağrılar

Mert Özen 8 Kas 2026 12 dk 3 görüntülenme
API4 Unrestricted Resource Consumption: Sayfa Boyutu, Sorgu Karmaşıklığı, Timeout'lar, Kota ve Maliyetli Dış Çağrılar

API4'e karşı her parametreye, her endpoint'e ve para harcatan her dış çağrıya bir üst sınır koyar ve bunu testle korursun. 200 000 siparişle ölçülen sınırsız listeden yola çıkıp sayfa boyutu, offset ve sort allowlist'i, toplu işlem ve body limiti, request timeout ile CancellationToken, tenant ve plan bazlı rapor ve SMS kotası ile 429 ve Retry-After'ı kuruyoruz.

Sürüm notu: Bu yazıdaki kodlar .NET 10 ve EF (Entity Framework) Core 10.0.14 ile derlenip denenmiştir; ölçümler Apple Silicon bir makinede, SQLite ile ve 200 000 siparişlik lab verisiyle alınmıştır.

Önce Tahmin Et

Lab'daki GET /orders endpoint'i ilk yazıdan beri bütün siparişleri tek seferde döndürüyor. Alan'ın üç siparişi varken bunun bir önemi yoktu. Şimdi Alan'ı iki yılda 200 000 sipariş vermiş bir kurumsal müşteri olarak düşün. Tek bir istek kaç megabayt döner, sunucunun belleği ne kadar şişer? Peki aynı istek dakikada yüz kez gelirse?

Bir de para tarafı var. Destek ekibi "siparişin durumunu SMS (Short Message Service) ile bildir" butonuna bastığında bir SMS sağlayıcısına para ödüyoruz. Ücretsiz plandaki bir tenant bu endpoint'i bir döngüye koyarsa faturayı kim öder?

Bu yazı API4'ün (OWASP API (Application Programming Interface) Security Top 10 2023'teki adıyla Unrestricted Resource Consumption) konusu. Pagination'ın temelini ASP.NET Core Web API serisinin pagination, filtering ve sorting yazısında, rate limiting'e girişi Web Güvenliği (OWASP Top 10) serisinin A06 Insecure Design yazısında görmüştük. Burada onların üstüne savunma olarak bakıyoruz: her parametre, her endpoint ve her dış çağrı için bir üst sınır ve bu sınırları koruyan testler.

API4 Neyi Kapsıyor?

OWASP'ın (Open Worldwide Application Security Project) API (Application Programming Interface) listesindeki API4 sayfası sınırlanması gereken kaynakları tek tek sayıyor: çalışma süresi, ayrılabilecek bellek, dosya tanıtıcıları ve process sayısı, yüklenebilecek dosya boyutu, tek istekte yapılabilecek işlem sayısı, sayfa başına kayıt sayısı ve üçüncü parti sağlayıcılardaki harcama limiti. Listeye dikkat edersen hepsi aynı soruya çıkıyor: bir istemci, tek bir istekle ya da çok sayıda istekle, bizim ödediğimiz bir kaynağı ne kadar tüketebilir?

Bu soruya tek bir katmanda cevap verilmiyor. Aşağıdaki görsel, lab'da bu yazının sonunda bir isteğin geçtiği limitleri ve her birinin hangi durum koduyla reddettiğini gösteriyor:

05-limit-layers-tr.png

Gateway katmanını ilerideki API Gateway ve YARP (Yet Another Reverse Proxy) serisine, Kestrel'in header ve bağlantı limitlerini ilerideki ASP.NET Core Güvenliği serisinin Kestrel yazısına bırakıyorum. Bu yazı uygulama içindeki katmanlara odaklanıyor: endpoint doğrulaması, request timeout, tenant ve plan bazlı kota.

Önce Ölç: Sınırsız Liste

Bir önceki yazının API'sini, içinde 200 000 sipariş olan bir veritabanıyla çalıştırdım. Siparişlerin hepsi Alan'ın, son iki yıla yayılmış. Liste endpoint'i hâlâ sınırsız:

$ curl -s -o /dev/null -w "%{http_code} %{size_download} bytes %{time_total}s\n" http://localhost:19514/orders -H "Authorization: Bearer $ALAN"
200 21800110 bytes 0.404194s
200 21800110 bytes 0.319672s

Tek istek 21,8 MB JSON (JavaScript Object Notation) döndü ve yaklaşık 0,3 saniye sürdü. İkinci istekten sonra process'in RSS'i (Resident Set Size) 340 MB civarındaydı. SQLite yerel diskte olduğu için bu sayılar iyimser; ağ üzerindeki bir veritabanında, mobil bir istemcide ya da aynı anda gelen yirmi istekte tablo çok daha kötü görünür. Önemli olan şu: bu maliyeti istemci seçiyor, sunucu değil.

Liste Parametrelerine Sınır

İlk savunma, istemcinin seçebildiği her parametreye bir üst sınır ve bir allowlist koymak. Yeni liste endpoint'i:

// İstemcinin seçebileceği sıralamalar; her biri index'li bir kolona ve kararlı bir ikinci anahtara dayanıyor
private static readonly Dictionary<string, Func<IQueryable<Order>, IOrderedQueryable<Order>>> OrderSorts =
    new(StringComparer.Ordinal)
    {
        ["createdAt"] = q => q.OrderBy(o => o.CreatedAt).ThenBy(o => o.Id),
        ["-createdAt"] = q => q.OrderByDescending(o => o.CreatedAt).ThenByDescending(o => o.Id),
        ["status"] = q => q.OrderBy(o => o.Status).ThenBy(o => o.Id)
    };
// Filtre yok: tenant ve müşteri kapsamını DbContext ekliyor; sayfa boyutu ve sıralama allowlist'te
orders.MapGet("/", async ([AsParameters] OrderListQuery query, OrdersDbContext db, CancellationToken ct) =>
{
    int page = query.Page ?? 1;
    int pageSize = query.PageSize ?? ResourceLimits.DefaultPageSize;
    string sort = query.Sort ?? "-createdAt";
    Dictionary<string, string[]> errors = [];
    if (pageSize is < 1 or > ResourceLimits.MaxPageSize)
    {
        errors["pageSize"] = [$"Must be between 1 and {ResourceLimits.MaxPageSize}."];
    }
    if (page < 1 || (long)(page - 1) * pageSize > ResourceLimits.MaxOffset)
    {
        errors["page"] = [$"Offset must not exceed {ResourceLimits.MaxOffset}; narrow the filter instead."];
    }
    if (!OrderSorts.TryGetValue(sort, out Func<IQueryable<Order>, IOrderedQueryable<Order>>? applySort))
    {
        errors["sort"] = [$"Allowed values: {string.Join(", ", OrderSorts.Keys)}."];
    }
    if (errors.Count > 0)
    {
        return Results.ValidationProblem(errors);
    }

    IQueryable<Order> filtered = query.Status is { } status ? db.Orders.Where(o => o.Status == status) : db.Orders;
    // Bir fazlasını okuyup HasMore'u buluyoruz; pahalı COUNT(*) yok
    List<OrderSummary> items = await applySort!(filtered)
        .Skip((page - 1) * pageSize)
        .Take(pageSize + 1)
        .Select(o => new OrderSummary(o.Id, o.Status.ToString(), o.Total, o.CreatedAt))
        .ToListAsync(ct);
    return Results.Ok(new PagedResult<OrderSummary>(items.Take(pageSize).ToList(), page, pageSize,
        items.Count > pageSize));
});

Burada dört karar var. Sayfa boyutunu sessizce 100'e kırpmak yerine 400 dönüyoruz: kırpma, "10 000 istedim, 100 geldi, demek ki 100 kayıt varmış" diye yanlış sonuç çıkaran istemciler üretiyor. Offset'e de bir tavan koyduk, çünkü Skip(100000) veritabanının o kadar satırı okuyup atması demek; derin sayfalama gerekiyorsa çözüm keyset pagination ya da daha dar bir filtre. Sıralama yalnız bir sözlükteki anahtarlarla yapılabiliyor; istemcinin gönderdiği string hiçbir zaman bir kolon adına ya da dinamik bir LINQ (Language Integrated Query) ifadesine dönüşmüyor. Her sıralama için (TenantId, CreatedAt) ve (TenantId, Status) index'lerini ekledim; allowlist'teki her anahtarın bir index'i olması kuralın kendisi. Son olarak toplam kayıt sayısını döndürmüyoruz: 200 000 satırlık bir tabloda her sayfa için COUNT(*) çalıştırmak listenin kendisinden pahalı olabilir. Bir fazla kayıt okuyup HasMore döndürmek istemcinin "sonraki sayfa var mı?" sorusunu cevaplamaya yetiyor.

Aynı veritabanında yeni endpoint:

$ curl -s -o /dev/null -w "%{http_code} %{size_download} bytes %{time_total}s\n" "http://localhost:19515/orders" -H "Authorization: Bearer $ALAN"
200 2229 bytes 0.085426s
200 2229 bytes 0.019689s
$ curl -s -o /dev/null -w "%{http_code} %{size_download} bytes %{time_total}s\n" "http://localhost:19515/orders?pageSize=100&sort=-createdAt" -H "Authorization: Bearer $ALAN"
200 10950 bytes 0.003492s
200 10950 bytes 0.001193s
$ curl -s "http://localhost:19515/orders?pageSize=5000&sort=internalCost" -H "Authorization: Bearer $ALAN"
{"type":"https://tools.ietf.org/html/rfc9110#section-15.5.1","title":"One or more validation errors occurred.","status":400,"errors":{"pageSize":["Must be between 1 and 100."],"sort":["Allowed values: createdAt, -createdAt, status."]},"traceId":"00-e9434e325cb2bf12386aa296d3ccd51b-786f2774e599ddbd-00"}
$ curl -s "http://localhost:19515/orders?page=600&pageSize=100" -H "Authorization: Bearer $ALAN"
{"type":"https://tools.ietf.org/html/rfc9110#section-15.5.1","title":"One or more validation errors occurred.","status":400,"errors":{"page":["Offset must not exceed 10000; narrow the filter instead."]},"traceId":"00-add53b493eb13d5c1a553d2920e1f7f5-b1b108920f22a44d-00"}

100 kayıtlık sayfa 10,9 KB ve sıcak çağrıda yaklaşık 1 milisaniye. Aynı mantık include ya da expand gibi ilişkili veriyi açan parametreler için de geçerli: izin verilen değerlerin listesi ve bir derinlik sınırı. Lab'da bu parametre yok, ama varsa expand=items.book.author.books gibi bir zincirin reddedilmesi gerekir.

Toplu İşlem ve Body Boyutu

İkinci yazıda yazdığımız toplu iptal endpoint'i istemciden bir id listesi alıyor. Listenin boyutu da bir kaynak: 100 000 id, 100 000 parametreli bir IN sorgusu demek. Bir üst sınır ekledik:

Guid[] ids = request.OrderIds.Distinct().ToArray();
if (ids.Length is 0 or > ResourceLimits.MaxBulkIds)
{
    return Results.ValidationProblem(new Dictionary<string, string[]>
    {
        ["orderIds"] = [$"Send between 1 and {ResourceLimits.MaxBulkIds} ids."]
    });
}

Body'nin kendisi de sınırlanmalı. Kestrel'in varsayılan request body limiti yaklaşık 30 MB; bir sipariş oluşturma isteği için bu çok fazla. Minimal API'de endpoint bazında sınırı metadata olarak veriyoruz:

})
.RequireAuthorization(LabPolicies.Customer)
// Sipariş body'si küçük: 16 KB üstü Kestrel'de 413 ile kesilir
.WithMetadata(new RequestSizeLimitAttribute(16 * 1024));
$ curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:19515/orders -H "Authorization: Bearer $ALAN" -H "Content-Type: application/json" --data-binary @big.json
413

Burada bir test tuzağına düştüm. Aynı senaryoyu WebApplicationFactory ile yazdığımda test 413 yerine 201 aldı: integration testlerdeki TestServer, Kestrel'in body boyutu limitini uygulamıyor. Test çıktısındaki log da bunu açıkça söylüyor:

warn: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[9]
      A request body size limit could not be applied. This server does not support the IHttpMaxRequestBodySizeFeature.

Bu yüzden test davranışı değil endpoint'in metadata'sını kontrol ediyor; davranışın kendisini yukarıdaki curl ile gerçek Kestrel'de gösterdim. Testin bir şeyi kanıtladığını sanıp aslında kanıtlamadığı durumlar güvenlik testlerinde en tehlikeli olanlar.

Request Timeout ve CancellationToken

Sınırlı parametreler bile pahalı bir sorgu üretebilir. Raporlama endpoint'i bir tarih aralığındaki günlük satışları gruplayarak döndürüyor. Aralığa bir üst sınır koyduk (92 gün), ama bir de zaman sınırı var. ASP.NET Core'un request timeout middleware'i bunu endpoint bazında policy olarak tanımlamaya izin veriyor:

services.AddRequestTimeouts(options =>
{
    // Her endpoint için varsayılan üst sınır; raporlar kendi policy'sini kullanır
    options.DefaultPolicy = new RequestTimeoutPolicy { Timeout = TimeSpan.FromSeconds(5) };
    options.AddPolicy(ReportsPolicy, configuration.GetValue("Limits:ReportTimeout", TimeSpan.FromSeconds(2)));
});
app.MapGet("/reports/daily-sales", async (DateOnly from, DateOnly to, OrdersDbContext db,
    CancellationToken ct) =>
{
    // Tarih aralığı da bir kaynak limiti: geniş aralık, pahalı sorgu
    if (to < from || to.DayNumber - from.DayNumber + 1 > ResourceLimits.MaxReportDays)
    {
        return Results.ValidationProblem(new Dictionary<string, string[]>
        {
            ["to"] = [$"The range must be between 1 and {ResourceLimits.MaxReportDays} days."]
        });
    }

    DateTime start = from.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
    DateTime end = to.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
    List<DailySales> rows = await db.Orders
        .Where(o => o.CreatedAt >= start && o.CreatedAt < end)
        .GroupBy(o => o.CreatedAt.Date)
        .Select(g => new DailySales(DateOnly.FromDateTime(g.Key), g.Count(), g.Sum(o => o.Total)))
        .ToListAsync(ct);
    return Results.Ok(rows.OrderBy(r => r.Day));
})
.RequireAuthorization(LabPolicies.Staff)
.RequireRateLimiting(ResourceLimits.ReportsPolicy)
.WithRequestTimeout(ResourceLimits.ReportsPolicy);

Middleware'in nasıl çalıştığını bilmek önemli: süre dolduğunda isteği zorla kesmiyor, yalnız HttpContext.RequestAborted token'ını iptal ediyor. Endpoint bu token'ı dinlemiyorsa sorgu sonuna kadar çalışır ve yanıt da döner. Minimal API'de CancellationToken parametresi bu token'a bağlanıyor ve biz onu ToListAsync(ct)'ye geçiriyoruz. Davranışı görmek için rapor policy'sini 10 milisaniyeye düşürüp 92 günlük raporu istedim:

$ Limits__ReportTimeout=00:00:00.010 dotnet bin/Debug/net10.0/Bookstore.Orders.Api.dll --urls http://localhost:19515
$ curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" "http://localhost:19515/reports/daily-sales?from=2026-08-01&to=2026-10-31" -H "Authorization: Bearer $MARGARET"
504 0.121207s
504 0.022781s
warn: Microsoft.AspNetCore.Http.Timeouts.RequestTimeoutsMiddleware[1]
      Timeout exception handled.

Normal ayarla (2 saniye) aynı rapor sıcak çağrıda 22 milisaniyede 200 dönüyor; 92 günden geniş aralık ise sorguya hiç ulaşmadan 400 alıyor. Microsoft'un dokümantasyonuna göre middleware debugger bağlıyken devre dışı kalıyor, bu yüzden timeout'u debug oturumunda test edip "çalışmıyor" sonucuna varma.

Pahalı Uçlar ve Para Harcatan Çağrılar: Plan Bazlı Kota

Rapor sorgusu geçerli parametrelerle bile ucuz değil, SMS ise doğrudan para. İkisi için de tenant başına ve plana göre değişen bir limit istiyoruz: ücretli plandaki North dakikada 10 rapor ve günde 500 SMS, ücretsiz plandaki South dakikada 2 rapor ve günde 3 SMS. Plan değerleri configuration'da:

"Plans": {
  "free": { "SmsPerDay": 3, "ReportsPerMinute": 2 },
  "paid": { "SmsPerDay": 500, "ReportsPerMinute": 10 }
}

Rate limiter policy'leri partition key olarak tenant'ı kullanıyor ve limit değerlerini tenant'ın planından alıyor:

// Pahalı rapor sorgusu: tenant başına dakikada plan kadar istek
options.AddPolicy(ReportsPolicy, context =>
{
    (string tenant, PlanLimits limits) = Tenant(context);
    return RateLimitPartition.GetTokenBucketLimiter($"reports:{tenant}", _ => new TokenBucketRateLimiterOptions
    {
        TokenLimit = limits.ReportsPerMinute,
        TokensPerPeriod = limits.ReportsPerMinute,
        ReplenishmentPeriod = TimeSpan.FromMinutes(1),
        QueueLimit = 0
    });
});

// Para harcatan çağrı: tenant başına günlük SMS kotası, plana göre
options.AddPolicy(SmsPolicy, context =>
{
    (string tenant, PlanLimits limits) = Tenant(context);
    return RateLimitPartition.GetFixedWindowLimiter($"sms:{tenant}", _ => new FixedWindowRateLimiterOptions
    {
        PermitLimit = limits.SmsPerDay,
        Window = TimeSpan.FromDays(1),
        QueueLimit = 0
    });
});

Partition seçimi senkron bir fonksiyon; içinden veritabanına gidemiyoruz. Bu yüzden TenantPlans adında küçük bir singleton, tenant'ların planlarını uygulama açılırken bir kez okuyor. Tenant claim'den geliyor, yani rate limiter middleware'i authentication'dan sonra çalışmalı; WebApplication authentication middleware'ini pipeline'ın başına eklediği için app.UseRateLimiter() bunu zaten sağlıyor. Reddedilen isteğe Retry-After header'ını OnRejected içinde, limiter'ın metadata'sından yazıyoruz.

$ for i in 1 2 3; do curl -s -o /dev/null -w "%{http_code} " -X POST http://localhost:19515/orders/0197f001-0000-7000-8000-000000000003/sms-notifications -H "Authorization: Bearer $BARBARA"; done
202 202 202
$ curl -i -X POST http://localhost:19515/orders/0197f001-0000-7000-8000-000000000003/sms-notifications -H "Authorization: Bearer $BARBARA"
HTTP/1.1 429 Too Many Requests
Content-Length: 0
Date: Sun, 08 Nov 2026 05:55:40 GMT
Server: Kestrel
Retry-After: 86400

$ for i in 1 2 3; do curl -s -o /dev/null -w "%{http_code} " "http://localhost:19515/reports/daily-sales?from=2026-10-01&to=2026-10-31" -H "Authorization: Bearer $BARBARA"; done
200 200 429

Retry-After: 86400 değerine dikkat et: kota birkaç saniye önce dolmuş olsa da fixed window limiter pencerenin tamamını, yani bir günü bildiriyor. ASP.NET Core 11 sürüm notları FixedWindowRateLimiter'ın artık bir sonraki pencere sınırına kalan süreyi doğru raporladığını yazıyor; .NET 10'da bu değeri olduğu gibi müşteriye göstermeden önce bunu bilmek iyi olur. Token bucket'ta değer 60 saniye, yani bir sonraki dolum.

Testler her limiti ayrı ayrı koruyor: liste parametreleri için yedi satırlık bir Theory, toplu işlem tavanı, rapor aralığı, iki tenant'ın farklı kotası ve her endpoint'te bir timeout policy'si olduğu.

passed ResourceLimitTests.Free_plan_gets_three_sms_per_day_then_429_with_retry_after (11ms)
passed ResourceLimitTests.List_parameters_are_bounded(url: "/orders?sort=internalCost", expected: 400) (1ms)
passed ResourceLimitTests.List_parameters_are_bounded(url: "/orders?page=502&pageSize=20", expected: 400) (0ms)
passed ResourceLimitTests.Report_quota_is_per_tenant_and_per_plan (1ms)
passed ResourceLimitTests.Every_endpoint_has_a_request_timeout (0ms)
passed ResourceLimitTests.Create_order_declares_a_small_body_limit (1ms)
Test run summary: Passed!
  total: 94
  failed: 0
  succeeded: 94
  skipped: 0
  duration: 1s 445ms

Üretimde Dikkat

ASP.NET Core'un rate limiter'ı bellekte çalışıyor: üç instance'lı bir API'de her instance kendi sayacını tutar ve günlük 3 SMS kotası fiilen 9 olur; uygulama yeniden başladığında sayaç sıfırlanır. Dakikalık hız limitleri için bu kabul edilebilir bir yaklaşıklık. Para harcatan kotalar için değil: günlük SMS bütçesi veritabanında ya da Redis'te, atomik bir artırma ile tutulmalı ve faturalama sistemiyle aynı sayıyı görmeli. Rate limiting'in çoklu instance tarafını ilerideki ASP.NET Core Güvenliği serisinin rate limiting yazısında ele alacağız.

Dış sağlayıcının kendi harcama limitini de aç. SMS, e-posta ya da LLM (Large Language Model) API'lerinin çoğunda hesap seviyesinde bütçe ve alarm ayarı var. Uygulama içindeki kota bir hata yüzünden devre dışı kalırsa son savunma o olur.

Limitleri ölç, tahmin etme. Lab'daki 100 kayıtlık sayfa sınırı ve 92 günlük rapor aralığı keyfi değil, gerçek ekranların ihtiyacına ve yukarıdaki ölçümlere göre seçildi. Bir limit gerçek kullanıcıları sürekli 429'a düşürüyorsa yanlış ayarlanmıştır; reddedilen istek oranını tenant bazında izlemek bunu gösterir. Telemetry tarafını serinin onuncu yazısında bağlayacağız.

Timeout'ları zincir olarak düşün. İstemcinin timeout'u, gateway'in timeout'u, request timeout ve veritabanı command timeout'u (EF Core'da varsayılan 30 saniye, log'larda CommandTimeout='30' olarak görünüyor) içten dışa doğru küçülmeli. Aksi hâlde istemci vazgeçtikten sonra sunucu dakikalarca çalışmaya devam eder.

Sık Düşülen Tuzaklar

Birinci tuzak, tüm API için tek bir global rate limit. "IP (Internet Protocol) adresi başına dakikada 100 istek" gibi bir kural ucuz endpoint'leri gereksiz yere kısıtlar, pahalı rapor endpoint'ini ise yeterince korumaz. Limit, endpoint'in maliyetine ve kimin ödediğine göre seçilmeli.

İkincisi, sayfa boyutunu istemciye bırakıp pageSize=int.MaxValue'yu test etmemek. Ya da sınırı koyup sıralama ve filtre parametrelerini unutmak: index'siz bir kolona göre sıralama, küçük bir sayfa için bile tam tablo taraması demek.

Üçüncüsü, CancellationToken'ı endpoint'e alıp alt katmanlara geçirmemek. Timeout middleware'i token'ı iptal eder, ama token'ı dinlemeyen bir sorgu çalışmaya devam eder ve kaynak tüketimi düşmez.

Dördüncüsü, kotayı yalnız kullanıcı bazında tutmak. Ücretsiz bir tenant yüz kullanıcı açarsa kullanıcı başına kota yüz katına çıkar. Para harcatan kaynaklarda bütçe, faturayı kim ödüyorsa onun üzerinden, yani tenant ve plan bazında tutulmalı.

Küçük Bir Deneme

★☆☆ Lab'ı Lab__BulkOrders=200000 ile başlat ve GET /orders?sort=status ile GET /orders?sort=-createdAt isteklerinin süresini karşılaştır. Index'i silip tekrar ölç.

★★☆ Liste endpoint'ine keyset pagination ekle: after parametresi son görülen (CreatedAt, Id) çiftini alsın ve offset tavanı bu modda uygulanmasın. 2000. sayfanın süresini iki yöntemle karşılaştır.

★★★ Günlük SMS kotasını rate limiter yerine veritabanında tut: tenant ve gün bazında bir sayaç tablosu ve atomik bir UPDATE ... SET Count = Count + 1 WHERE Count < @limit. İki instance'ı aynı veritabanına bağlayıp kotanın aşılmadığını göster.

Bir sonraki yazıda API5'e (Broken Function Level Authorization) geçiyoruz: uygulamadaki bütün endpoint'leri EndpointDataSource ile listeleyip her birinin bir authorization kararı taşıdığını doğrulayan, yeni endpoint matrise girmeden build'i kıran bir test ve admin API'lerini ayrı bir host ve port'a taşımak.