← Back to list

API Resiliency — Bölüm 1: Gateway, Authentication & Authorization, Rate Limiting

Modern bir backend sistemi geliştirirken en sık karşılaşılan sorulardan biri şudur: “Uygulamam büyüyünce ne olacak?”

Onur Nafi Güzel · 2026-04-14 12:41 · 1 claps · 9.8 min read
#api-rate-limiter #authentication #authorization #api-gateway #system-resiliency
Open on Medium ↗
Wiki topics: LIT · Literature & Writing 🌐 · Web Development

API Resiliency — Bölüm 1: Gateway, Authentication & Authorization, Rate Limiting

Modern bir backend sistemi geliştirirken en sık karşılaşılan sorulardan biri şudur: “Uygulamam büyüyünce ne olacak?”

Tek bir servisin her şeyi hallettigi bir yapı, başlangıçta pratik görünür. Ama zaman içinde bu yapı; güvenlik açıkları, ölçeklenme sorunları ve bakım kabuslarına dönüşebilir. İşte API Resiliency bu noktada devreye girer.

Bu seri boyunca, sıfırdan bir .NET backend mimarisi kurarak aşağıdaki konuları ele alacağız:

Bölüm 1: Gateway, Authentication & Authorization, Rate Limiting

Bu ilk bölümde şunları anlatacağım:

✅ API Gateway nedir ve neden gereklidir ✅ .NET’te YARP ile gateway nasıl kurulur ✅ JWT tabanlı authentication nasıl uygulanır ✅ Rol bazlı authorization nasıl yapılandırılır ✅ Rate limiting ile brute force ve DoS saldırılarına karşı nasıl korunulur

Makale boyunca lokalimde çalıştığım proje ChangeMind üzerinden ilerleyeceğiz. Daha detaylı inceleme için github adresini ziyaret edebilirsiniz. ChangeMind; koçlar, kullanıcılar, paketler ve ödeme işlemlerini yöneten bir fitness koçluk platformudur.

Proje Yapısı

ChangeMind, Clean Architecture prensiplerine göre yapılandırılmıştır:

ChangeMind/
├── src/
│   ├── ChangeMind.Api/            → HTTP entry point, controller'lar
│   ├── ChangeMind.Application/    → Use case'ler, iş mantığı
│   ├── ChangeMind.Domain/         → Entity'ler, enum'lar, kurallar
│   ├── ChangeMind.Infrastructure/ → DB, dış servisler, JWT
│   └── ChangeMind.Gateway/        → API Gateway (YARP)

Bağımlılık yönü şöyledir:

ChangeMind.Api
    └── ChangeMind.Application
    └── ChangeMind.Infrastructure
            └── ChangeMind.Domain

Bölüm 1: API Gateway

Neden Gerekli?

ChangeMind’ın onlarca endpoint’i var: kullanıcı kaydı, koç yönetimi, paket satın alma, ödeme işlemleri. Her birinin kendi kimlik doğrulama kuralları, erişim politikaları ve yönlendirme mantığı var.

Tüm bunları her serviste ayrı ayrı yönetmek hem tekrar (duplication) hem de hata odağı yaratır. API Gateway, tüm bu kesişen endişeleri (cross-cutting concerns) tek bir noktada toplar:

İstemci
    │
    │  HTTP istekleri
    ▼
┌──────────────────────────────────┐
│       ChangeMind.Gateway         │
│  ┌──────────────────────────┐    │
│  │  JWT Doğrulama           │    │
│  │  Policy Kontrolü         │    │
│  │  Route Eşleştirme        │    │
│  └──────────────────────────┘    │
│  :5000                           │
└───────────────┬──────────────────┘
                │
                ▼
┌──────────────────────────────────┐
│       ChangeMind.Api             │
│  :5123 (sadece iç ağda açık)     │
└──────────────────────────────────┘

Gateway’in üstlendiği sorumluluklar:

 Sorumluluk    | Açıklama                             
---------------|--------------------------------------
 Routing       | İsteği doğru servise yönlendir       
 Authentication| Token geçerli mi?                     
 Authorization | Bu kullanıcı bu kaynağa erişebilir mi?
 Rate Limiting | Saniyede kaç istek geçebilir?
 Health Check  | Servis ayakta mı?

Hangi Teknoloji?

.NET ekosisteminde iki popüler seçenek var: Ocelot ve YARP. Ben YARP üzerinden gideceğim. YARP: (Yet Another Reverse Proxy)

Microsoft tarafından geliştirilen, .NET 8+ ile tam uyumlu ve aktif olarak geliştirilen bir çözüm.

YARP Kurulumu

dotnet add package Yarp.ReverseProxy

// src/ChangeMind.Gateway/Program.cs

builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

// Middleware sırası kritik!
app.UseAuthentication();
app.UseAuthorization();

app.MapReverseProxy();

app.Run();

Route Konfigürasyonu (appsettings.json)

YARP, route’ları appsettings.json üzerinden okur. Her route için hangi endpoint'in hangi politikayla korunduğunu buradan yönetiyoruz:

// src/ChangeMind.Gateway/appsettings.json
{
  "Urls": "http://localhost:5000",
  "Jwt": {
    "Secret": "your-super-secret-jwt-key-min-32-characters-long!",
    "Issuer": "ChangeMind",
    "Audience": "ChangeMindApi",
    "ExpirationMinutes": 60
  },
  "ReverseProxy": {
    "Routes": {
      "auth-login": {
        "ClusterId": "changemind-api",
        "Match": {
          "Path": "/api/auth/login",
          "Methods": ["POST"]
        }
        // AuthorizationPolicy yok → Herkese açık (login endpoint'i)
      },
      "users-waiting": {
        "ClusterId": "changemind-api",
        "AuthorizationPolicy": "coach-or-admin",
        "Match": {
          "Path": "/api/users/waiting",
          "Methods": ["GET"]
        }
      },
      "users-list": {
        "ClusterId": "changemind-api",
        "AuthorizationPolicy": "coach-or-admin",
        "Match": {
          "Path": "/api/users",
          "Methods": ["GET"]
        }
      },
      "payments": {
        "ClusterId": "changemind-api",
        "AuthorizationPolicy": "auth",
        "Match": {
          "Path": "/api/payments",
          "Methods": ["POST"]
        }
      }
    },
    "Clusters": {
      "changemind-api": {
        "Destinations": {
          "destination1": {
            "Address": "http://localhost:5123"
          }
        }
      }
    }
  }
}

📦 Ne Yaptık? (Bölüm 1 Özeti)

  • YARP paketini Gateway projesine ekledik
  • appsettings.json üzerinden 4farklı route tanımladık
  • Her route için hangi policy’nin uygulanacağını belirledik
  • /health endpoint'i ile Gateway'in sağlık durumunu kontrol edebilir hale geldik
  • Artık tüm istekler Gateway’den geçiyor.

Bölüm 2: Authentication

Neden Gerekli?

Authentication, “bu kullanıcı kim?” sorusunu cevaplar. ChangeMind’da üç farklı rol var: Admin, Coach ve User. Her rolün farklı yetkisi var ve sistem bunu token içindeki bilgiye göre anlıyor.

Doğrulanmamış bir isteğin sisteme girmesi; veri sızıntısı, yetkisiz işlem ve kötü niyetli saldırıların kapısını açar.

JWT Nasıl Çalışır?

1. Kullanıcı login → API, imzalı JWT üretir
2. Kullanıcı her istekte → Authorization: Bearer <token> gönderir
3. Gateway → Token'ı doğrular (imza + süre + issuer + audience)
4. Geçerliyse → İsteği API'ye yönlendir
5. Geçersizse → 401 Unauthorized dön

JWT üç parçadan oluşur:

Header.Payload.Signature

eyJhbGciOiJIUzI1NiJ9           → Header (algoritma: HS256)
.eyJzdWIiOiIxMjM0NTY3ODkwIn0   → Payload (claim'ler: userId, email, role)
.SflKxwRJSMeKKF2QT4fwpMeJf36   → Signature (HMAC-SHA256 imzası)

⚠️ Önemli: JWT payload’u yalnızca Base64 ile encode edilmiştir, şifrelenmemiştir. Payload içeriği herkes tarafından okunabilir. Şifre, kredi kartı veya hassas kişisel veri asla token’a konulmamalıdır.

Infrastructure Katmanı: TokenService

Paket kurulumu:

dotnet add package System.IdentityModel.Tokens.Jwt
dotnet add package Microsoft.IdentityModel.Tokens

TokenService, JWT token'ı oluşturur. Token içine userId, email ve role claim'lerini gömer ve secret key ile imzalar:

public class TokenService(JwtSettings jwtSettings) : ITokenService
{
    public (string AccessToken, string RefreshToken) GenerateTokens(
        Guid userId, string email, UserRole role)
    {
        var accessToken = GenerateAccessToken(userId, email, role);
        var refreshToken = GenerateRefreshToken();
        return (accessToken, refreshToken);
    }

    public string GenerateRefreshToken()
    {
        var randomNumber = new byte[64];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(randomNumber);
        }
        return Convert.ToBase64String(randomNumber);
    }

    private string GenerateAccessToken(Guid userId, string email, UserRole role)
    {
        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(jwtSettings.Secret));

        var credentials = new SigningCredentials(
            key, SecurityAlgorithms.HmacSha256);

        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
            new Claim(ClaimTypes.Email, email),
            new Claim(ClaimTypes.Role, role.ToString())  // "Admin" | "Coach" | "User"
        };

        var token = new JwtSecurityToken(
            issuer: jwtSettings.Issuer,
            audience: jwtSettings.Audience,
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(jwtSettings.AccessTokenExpiryMinutes),
            signingCredentials: credentials);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}
{  
  "Jwt": {
    "Secret": "this-is-a-super-secret-key-that-must-be-at-least-32-characters-long-change-in-production",
    "Issuer": "ChangeMindApi",
    "Audience": "ChangeMindApp",
    "AccessTokenExpiryMinutes": 15,
    "RefreshTokenExpiryDays": 7
  }
}

⚠️ Güvenlik Uyarısı: Secret değerini production ortamında appsettings.json'a yazma. Environment variable veya bir secret manager kullan:

# Linux/macOS
export Jwt__Secret="production-secret-key-here"

# Windows PowerShell
$env:Jwt__Secret = "production-secret-key-here"

# Docker
docker run -e Jwt__Secret="production-secret" myapp

🔐 Ne Yaptık? (Bölüm 2 Özeti)

  • JWT token’ın nasıl çalıştığını anladık

Bölüm 3: Authorization

Neden Gerekli?

Authentication “kim olduğunu” söyler; Authorization ise “ne yapabileceğini” belirler. ChangeMind’da erişim kuralları şöyle:

İşlem                      Kim Yapabilir?
------------------------------------------
Koç oluştur                Sadece Admin
Tüm kullanıcıları listele  Coach veya Admin
Bekleyen kullanıcıları gör Coach veya Admin
Kendi profilini güncelle   Herhangi bir doğrulanmış kullanıcı
Kayıt ol                   Herkese açık
Login                      Herkese açık

Gateway’de Security Extension’ı

Gateway, token’ı API ile aynı secret key kullanarak doğrular:

public static class SecurityServiceExtensions
{
    public static IServiceCollection AddGatewaySecurity(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var jwtSettings = configuration.GetSection("Jwt").Get<JwtSettings>()
            ?? throw new InvalidOperationException(
                "JWT settings not found in Gateway configuration.");

        services.AddSingleton(jwtSettings);

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(jwtSettings.Secret)),
                    ValidateIssuer   = true,
                    ValidIssuer      = jwtSettings.Issuer,
                    ValidateAudience = true,
                    ValidAudience    = jwtSettings.Audience,
                    ValidateLifetime = true,
                    // Token süresi dolduğunda anında geçersiz say — 0 tolerans
                    ClockSkew        = TimeSpan.Zero
                };
            });

        // Yedek policy'ler (Program.cs'deki ile aynı isimleri koruyun)
        services.AddAuthorization(options =>
        {
            options.AddPolicy("AdminOnly",      policy => policy.RequireRole("Admin"));
            options.AddPolicy("CoachOrAdmin",   policy => policy.RequireRole("Coach", "Admin"));
        });

        return services;
    }
}

ClockSkew = TimeSpan.Zero neden önemli? Varsayılan değer 5 dakikadır; yani süresi dolmuş bir token 5 dakika daha geçerli sayılır. Bunu sıfıra indirerek token süresi biter bitmez isteği reddediyoruz.

Policy’ler Gateway’den Route’lara Bağlanıyor

appsettings.json'daki AuthorizationPolicy alanı, YARP'a "bu route'a gelen isteği hangi policy ile kontrol et?" diye söyler:

"coaches-create": {
  "ClusterId": "changemind-api",
  "AuthorizationPolicy": "admin-only",   ← Bu satır yeterli!
  "Match": {
    "Path": "/api/coaches",
    "Methods": ["POST"]
  }
}

İstek bu route’a geldiğinde YARP otomatik olarak:

  1. Authorization: Bearer <token> header'ını okur
  2. Token’ı doğrular
  3. Token’daki role claim'ini admin-only policy'ye göre kontrol eder

Ne Yaptık? (Bölüm 3 Özeti)

  • Gateway seviyesinde 3 policy tanımladık: auth, admin-only, coach-or-admin
  • appsettings.json'da her route'a uygun policy atadık
  • ClockSkew = TimeSpan.Zero ile token süre toleransını kaldırdık

Bölüm 4: Rate Limiting

Neden Gerekli?

Authentication ve authorization, kim olduğunu ve neye erişebileceğini kontrol eder. Ancak geçerli bir token’a sahip biri bile sistemi kasıtlı ya da kasıtsız olarak zorlayabilir:

  • Bir saldırgan saniyede yüzlerce login denemesi yaparak şifreler kırmaya çalışabilir (brute force)
  • Bir bot saniyede binlerce kayıt isteği göndererek veritabanını şişirebilir (spam)
  • Meşru bir kullanıcı bile hatalı bir döngüyle sistemi bunaltabilir (accidental DoS)

Rate limiting, bir IP veya istek kaynağının belirli bir süre içinde kaç istek gönderebileceğini sınırlar. Limit aşılırsa Gateway isteği API’ye iletmeden 429 Too Many Requests ile reddeder.

İstemci (hızlı istek)
    │
    ▼
Gateway: Rate Limiter
    ├── ✅ Limit içinde  → API'ye yönlendir
    └── ❌ Limit aşıldı → 429 + Retry-After header → API'ye ulaşmaz

Hangi Teknoloji?

.NET 7'den itibaren Microsoft.AspNetCore.RateLimiting SDK içinde geliyor ekstra NuGet paketi gerekmez. YARP da route konfigürasyonunda RateLimiterPolicy alanını destekler; böylece her route'a farklı bir policy atayabiliriz.

Algoritma Seçimi

Rate limiting için birkaç farklı algoritma vardır. ChangeMind’da endpoint’in tehdit profiline göre üç farklı algoritma kullandım:

Fixed Window (Sabit Pencere)

Zaman:   |── 60 sn ──|── 60 sn ──|
İstekler:  ●●●●●        ●●●
Limit:     5            5

Her 60 saniyelik pencerede en fazla N istek. Pencere sıfırlandığında sayaç da sıfırlanır. Basit ve bellek dostu. Login ve şifre değiştirme gibi brute force hedefli endpoint’ler için idealdir, pencere dolunca kesin dur.

Zayıf noktası: Pencere sonu ve başında arka arkaya 2×N istek gönderilebilir (boundary burst). Bu nedenle genel endpoint’lerde sliding window tercih edilir.

Sliding Window (Kayan Pencere)

Şimdi: t=45s
Pencere: [t-60s, t] → son 60 saniyede kaç istek?

Son 60 saniyede gönderilen toplam istek sayısına bakar; pencere sürekli “kayar”. Boundary burst problemini ortadan kaldırır. Authenticated genel endpoint’ler için kullanılır.

Token Bucket (Jeton Kovası)

Kova: max 30 token
Her 10s: +10 token eklenir (replenish)
Her istek: 1 token tüketir

t=0:  [●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●] 30 token
t=1:  10 istek → [●●●●●●●●●●●●●●●●●●●●] 20 token
t=5:  15 istek → [●●●●●] 5 token
t=10: +10 replenish → [●●●●●●●●●●●●●●●] 15 token

Kısa süreli burst’e (ani yoğunluk) izin verir, uzun vadede dengeli bir hız elde edilir. Admin işlemleri gibi nadiren çağrılan ama arada toplu gelebilen endpoint’ler için uygundur.

Policy Tasarımı

Policy   | Algoritma      | Limit             | Pencere | Kullanım 
strict   | Fixed Window   | 5 istek           | 60 sn | Login, şifre değiştirme 
public   | Fixed Window   | 20 istek          | 60 sn | Kullanıcı kaydı 
standard | Sliding Window | 60 istek          | 60 sn | Authenticated endpointler 
admin    | Token Bucket   | 30 token, 10/10sn | -     | Admin işlemleri

Gateway Extension’ı


namespace ChangeMind.Gateway.Extensions;

public static class RateLimitingServiceExtensions
{
    public static IServiceCollection AddGatewayRateLimiting(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var strict   = configuration.GetSection("RateLimiting:Strict");
        var pub      = configuration.GetSection("RateLimiting:Public");
        var standard = configuration.GetSection("RateLimiting:Standard");
        var admin    = configuration.GetSection("RateLimiting:Admin");

        services.AddRateLimiter(options =>
        {
            options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

            // Limit aşılınca Retry-After header ekle ve anlamlı mesaj döndür
            options.OnRejected = async (context, token) =>
            {
                context.HttpContext.Response.Headers.RetryAfter =
                    context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)
                        ? ((int)retryAfter.TotalSeconds).ToString()
                        : "60";

                await context.HttpContext.Response.WriteAsync(
                    "Too many requests. Please try again later.", token);
            };

            // Strict — Login, change-password: brute force koruması
            options.AddFixedWindowLimiter("strict", opt =>
            {
                opt.PermitLimit          = strict.GetValue("PermitLimit", 5);
                opt.Window               = TimeSpan.FromSeconds(strict.GetValue("WindowSeconds", 60));
                opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
                opt.QueueLimit           = 0;
            });

            // Public — Register: kayıt spam koruması
            options.AddFixedWindowLimiter("public", opt =>
            {
                opt.PermitLimit          = pub.GetValue("PermitLimit", 20);
                opt.Window               = TimeSpan.FromSeconds(pub.GetValue("WindowSeconds", 60));
                opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
                opt.QueueLimit           = 0;
            });

            // Standard — Authenticated endpoint'ler: boundary burst'e karşı sliding window
            options.AddSlidingWindowLimiter("standard", opt =>
            {
                opt.PermitLimit          = standard.GetValue("PermitLimit", 60);
                opt.Window               = TimeSpan.FromSeconds(standard.GetValue("WindowSeconds", 60));
                opt.SegmentsPerWindow    = 6;   // 60s / 6 = 10s segmentler
                opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
                opt.QueueLimit           = 0;
            });

            // Admin — Admin işlemleri: burst'e izin veren token bucket
            options.AddTokenBucketLimiter("admin", opt =>
            {
                opt.TokenLimit               = admin.GetValue("TokenLimit", 30);
                opt.ReplenishmentPeriod      = TimeSpan.FromSeconds(admin.GetValue("ReplenishmentPeriodSeconds", 10));
                opt.TokensPerPeriod          = admin.GetValue("TokensPerPeriod", 10);
                opt.AutoReplenishment        = true;
                opt.QueueProcessingOrder     = QueueProcessingOrder.OldestFirst;
                opt.QueueLimit               = 0;
            });
        });

        return services;
    }
}

Birkaç tasarım kararı:

QueueLimit = 0: Bekleyen istek kuyruğu yok. Limit dolduğunda istek anında reddedilir. Kuyruk açılsaydı istemci 429 yerine gecikmeli 200 alırdı; bu, botların sistemi yavaşça zorlayabileceği anlamına gelir.

SegmentsPerWindow = 6: Sliding window, 60 saniyelik pencereyi 6 adet 10 saniyelik segmente böler. Segment sayısı arttıkça hassasiyet artar, bellek kullanımı da artar. 6 değeri makul bir dengedir.

OnRejected: Retry-After header'ı istemciye "kaç saniye sonra tekrar dene" bilgisini verir. Token bucket için bu değer Lease nesnesinden dinamik hesaplanır; diğerleri için sabit 60 saniye kullanılır.

Configuration (appsettings.json)

Limit değerleri kod içine gömülmemiş — konfigürasyondan okunuyor. Bu sayede production’da yeniden deploy gerekmeden değer ayarlanabilir:

{
  "RateLimiting": {
    "Strict":   { "PermitLimit": 5,  "WindowSeconds": 60 },
    "Public":   { "PermitLimit": 20, "WindowSeconds": 60 },
    "Standard": { "PermitLimit": 60, "WindowSeconds": 60 },
    "Admin":    { "TokenLimit": 30, "ReplenishmentPeriodSeconds": 10, "TokensPerPeriod": 10 }
  }
}

Route’lara Policy Atamak

YARP’ın kullanımı çok basit AuthorizationPolicy gibi RateLimiterPolicy da doğrudan route konfigürasyonuna yazılır:

"auth-login": {
  "ClusterId": "changemind-api",
  "RateLimiterPolicy": "strict",      ← 5 istek / 60 sn
  "Match": {
    "Path": "/api/auth/login",
    "Methods": ["POST"]
  }
},
"users-create": {
  "ClusterId": "changemind-api",
  "RateLimiterPolicy": "public",      ← 20 istek / 60 sn
  "Match": {
    "Path": "/api/users",
    "Methods": ["POST"]
  }
},
"users-waiting": {
  "ClusterId": "changemind-api",
  "AuthorizationPolicy": "coach-or-admin",
  "RateLimiterPolicy": "standard",    ← 60 istek / 60 sn (sliding)
  "Match": {
    "Path": "/api/users/waiting",
    "Methods": ["GET"]
  }
},
"coaches-create": {
  "ClusterId": "changemind-api",
  "AuthorizationPolicy": "admin-only",
  "RateLimiterPolicy": "admin",       ← Token bucket, burst'e izin verir
  "Match": {
    "Path": "/api/coaches",
    "Methods": ["POST"]
  }
}

Tüm route → policy eşleşmesi:

Route                                   RateLimiterPolicy
POST /api/auth/login                    strict
POST /api/users/{id}/change-password    strict
POST /api/users (kayıt)                 public
POST /api/coaches                       admin
POST /api/coaches/{id}/change-password  admin
Diğer tüm authenticated endpointler     standard

Middleware Sırası

// src/ChangeMind.Gateway/Program.cs
builder.Services.AddGatewaySecurity(builder.Configuration);
builder.Services.AddGatewayRateLimiting(builder.Configuration);  // ← eklendi

// ...

app.UseRateLimiter();       // ← önce rate limit kontrol et
app.UseAuthentication();    // ← sonra token doğrula
app.UseAuthorization();     // ← sonra yetki kontrol et
app.MapReverseProxy();

UseRateLimiter() neden en önce? Rate limiter, isteği API'ye iletmeden önce reddetmeli. Eğer UseAuthentication()'dan sonra koyulsaydı, her reddedilen istek yine de token doğrulaması yükü yaratırdı. Doğru sıra şu maliyeti engeller:

Limit aşıldı → UseRateLimiter() → 429 → İşlem bitti
              (token doğrulama + DB sorgusu yok)

⚡ Ne Yaptık? (Bölüm 4 Özeti)

  • Ekstra paket kurmadan .NET SDK’nın built-in rate limiter’ını kullandık
  • Tehdit profiline göre 4 farklı policy tasarladık: strict, public, standard, admin
  • Her YARP route’una RateLimiterPolicy atadık — tek satır JSON yeterli
  • UseRateLimiter() middleware'ini authentication'dan önce konumlandırdık
  • Limit değerlerini appsettings.json'a taşıdık — deploy gerekmeden ayarlanabilir

Genel Mimari Resmi

Bu noktada sistemin tam akışı şöyle:

İstemci
    │
    │  POST /api/auth/login
    ▼
┌──────────────────────────────────────┐
│        ChangeMind.Gateway            │
│  ┌────────────────────────────────┐  │
│  │  Route: auth-login             │  │
│  │  Policy: YOK (herkese açık)    │  │
│  └────────────────────────────────┘  │
│  :5000                               │
└──────────────┬───────────────────────┘
               │  Yönlendir → :5123
               ▼
┌──────────────────────────────────────┐
│         ChangeMind.Api               │
│  AuthController.Login()              │
│  → LoginCommandHandler               │
│  → TokenService.GenerateTokens()     │
│  ← { accessToken, refreshToken }     │
└──────────────────────────────────────┘
               │
               │  { accessToken: "eyJ..." }
               ▼
           İstemci

    ─────────────────────────────────
    Sonraki istek: Korumalı endpoint
    ─────────────────────────────────

İstemci
    │
    │  GET /api/users/waiting
    │  Authorization: Bearer eyJ...
    ▼
┌──────────────────────────────────────┐
│        ChangeMind.Gateway            │
│  ┌────────────────────────────────┐  │
│  │  Route: users-waiting          │  │
│  │  RateLimiterPolicy: standard   │  │
│  │  AuthorizationPolicy: coach-or-admin│
│  │                                │  │
│  │  1. Rate limit kontrol         │  │
│  │     ❌ Aşıldı → 429 dön        │  │
│  │  2. Token doğrula (imza+süre)  │  │
│  │     ❌ Geçersiz → 401 dön      │  │
│  │  3. Role: "Coach" veya "Admin" │  │
│  │     ❌ Yetersiz → 403 dön      │  │
│  │     ✅ Geçti → yönlendir       │  │
│  └────────────────────────────────┘  │
└──────────────┬───────────────────────┘
               │  Yönlendir → :5123
               ▼
┌──────────────────────────────────────┐
│         ChangeMind.Api               │
│  UsersController.GetWaitingUsers()   │
└──────────────────────────────────────┘

Güvenlik Kontrol Listesi

Bölümü tamamlamadan önce şunları kontrol et:

  • JWT Secret key en az 32 karakter uzunluğunda
  • Secret key production’da environment variable veya secret manager’da saklanıyor
  • ClockSkew = TimeSpan.Zero ayarlı (token süre toleransı yok)
  • app.UseRateLimiter()app.UseAuthentication()app.UseAuthorization() şeklinde middleware sırası olmalı
  • Hassas endpoint’ler Gateway’de policy ile korunuyor
  • Login ve şifre değiştirme endpoint’lerinde strict rate limiting aktif
  • Rate limit aşımında Retry-After header dönüyor
  • Rate limit değerleri appsettings.json'da, kodda hard-code değil
  • ChangeMind.Api (:5123) dış ağa kapalı, sadece Gateway üzerinden erişilebilir. Cors:AllowedOrigins
  • Token payload’ında hassas veri yok (şifre, kredi kartı numarası vs.)

Bu yapı sayesinde:

  • Tüm güvenlik kuralları tek noktada (appsettings.json) yönetiliyor
  • Yeni bir route eklemek sadece birkaç satır JSON
  • Token manipülasyonu, kriptografik imza sayesinde mümkün değil
  • ChangeMind.Api doğrudan dışarıya açık değil
  • Brute force ve DoS saldırıları API’ye ulaşmadan Gateway’de engelleniyor

메타데이터
post_id
f5a19451d962
slug
api-resiliency-bölüm-1-gateway-authentication-authorization-rate-limiting-f5a19451d962
url
https://medium.com/@ongguzel/api-resiliency-b%C3%B6l%C3%BCm-1-gateway-authentication-authorization-rate-limiting-f5a19451d962
canonical_url
https://medium.com/@ongguzel/api-resiliency-b%C3%B6l%C3%BCm-1-gateway-authentication-authorization-rate-limiting-f5a19451d962
author_url
https://medium.com/@ongguzel
status
ok
fetched_at
2026-07-31 04:15:11