Microservices Architecture · Lesson 3 of 7
API Gateway Pattern — Routing, Auth, Rate Limiting
What the API Gateway Does
API Gateway: the single entry point for all client requests to your microservices.
Responsibilities:
✓ Request routing: /api/patients → PatientService, /api/prescriptions → PrescriptionService
✓ Authentication: validate JWT once at the gateway, pass identity downstream
✓ Rate limiting: protect services from traffic spikes and abuse
✓ SSL termination: TLS at the gateway, plain HTTP internally
✓ Request aggregation: combine results from multiple services for one client call
✓ Load balancing: distribute requests across service instances
What it does NOT replace:
✗ Authorization (each service still validates what the user can access)
✗ Business logic (the gateway is infrastructure, not application logic)YARP — .NET Reverse Proxy
// NuGet: Yarp.ReverseProxy
// Program.cs
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
app.MapReverseProxy();
// appsettings.json
{
"ReverseProxy": {
"Routes": {
"patient-route": {
"ClusterId": "patient-cluster",
"Match": { "Path": "/api/patients/{**catch-all}" }
},
"prescription-route": {
"ClusterId": "prescription-cluster",
"Match": { "Path": "/api/prescriptions/{**catch-all}" }
}
},
"Clusters": {
"patient-cluster": {
"Destinations": {
"patient-1": { "Address": "http://patient-service:8080/" },
"patient-2": { "Address": "http://patient-service-2:8080/" }
}
},
"prescription-cluster": {
"Destinations": {
"prescription-1": { "Address": "http://prescription-service:8080/" }
}
}
}
}
}Centralized JWT Validation
// Validate JWT at the gateway — downstream services trust the gateway
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://auth.systemforge.internal";
options.Audience = "systemforge-api";
});
app.UseAuthentication();
app.UseAuthorization();
// Pass user claims downstream via headers
app.MapReverseProxy(proxyPipeline =>
{
proxyPipeline.Use(async (context, next) =>
{
if (context.User.Identity?.IsAuthenticated == true)
{
var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
var roles = context.User.FindAll(ClaimTypes.Role)
.Select(c => c.Value)
.ToList();
context.Request.Headers["X-User-Id"] = userId;
context.Request.Headers["X-User-Roles"] = string.Join(",", roles);
}
await next();
});
});
// Downstream services read X-User-Id from header — no need to validate JWT again
// They trust the gateway (mutual TLS or network policy enforces trust boundary)Rate Limiting
// NuGet: System.Threading.RateLimiting (built into .NET 7+)
builder.Services.AddRateLimiter(options =>
{
// Global: 100 requests per minute per IP
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
context => RateLimitPartition.GetFixedWindowLimiter(
partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "anonymous",
factory: _ => new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1),
}));
// Per-endpoint: prescription creation is more expensive
options.AddFixedWindowLimiter("prescriptions", opts =>
{
opts.PermitLimit = 20;
opts.Window = TimeSpan.FromMinutes(1);
opts.QueueLimit = 5;
});
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});
app.UseRateLimiter();BFF vs API Gateway
API Gateway (general):
Single gateway for all clients (web, mobile, third-party).
General-purpose routing and auth.
Clients adapt to the API format.
Backend for Frontend (BFF):
Dedicated gateway per client type.
Web BFF: aggregates data specifically for the web dashboard
Mobile BFF: aggregates data optimized for mobile payload size
Third-party BFF: versioned, stable API for external partners
When to use BFF:
→ Different clients need very different data shapes
→ Mobile needs smaller payloads than web
→ Different teams own each frontend
→ Third-party API must be stable while internal services change
Most clinical systems: one API Gateway for external, BFF pattern for specific
high-traffic client types (mobile app, ward dashboard).Production issue I've seen: A team put JWT validation in every microservice individually. When the JWT secret rotated, they had to update 8 services simultaneously. Two services were missed — they continued accepting tokens signed with the old secret for 3 days after rotation. Centralizing JWT validation at the API gateway meant the rotation was a single configuration change in one place.
Key Takeaway
The API Gateway is the single entry point: routing, auth, rate limiting, SSL termination. YARP is the .NET-native reverse proxy for building API gateways. Validate JWT at the gateway and pass identity headers downstream — don't re-validate in every service. Use BFF when different clients need substantially different APIs. The gateway is infrastructure, not business logic — keep it thin.