Skip to content

Rate Limiting

Rate limiting in Microtec is enforced at two layers: the Azure Front Door WAF (global tier, IP-based) and the Ocelot API Gateway (per-client, per-route tier). This two-tier approach prevents both volumetric attacks and misbehaving API clients from degrading service for other tenants.


Two-Tier Architecture

TierEnforcerScopeLimit
1 — NetworkAzure Front Door WAFPer source IP10,000 requests / 5 minutes
2 — ApplicationOcelot API GatewayPer client token / tenantConfigurable per route

Ocelot Rate Limiter Configuration

Ocelot's built-in rate limiting middleware is configured in Platforms/Src/Gateway/ocelot.{env}.json:

Global Defaults

json
{
  "GlobalConfiguration": {
    "RateLimitOptions": {
      "ClientWhitelist": ["internal-services"],
      "EnableRateLimiting": true,
      "Period": "1m",
      "PeriodTimespan": 60,
      "Limit": 200,
      "HttpStatusCode": 429,
      "QuotaExceededMessage": "API rate limit exceeded. Please retry after {0} seconds.",
      "RateLimitCounterPrefix": "ocelot"
    }
  }
}

The ClientWhitelist entry "internal-services" exempts requests with a valid XApiKey header from rate limiting. Internal microservice calls are never rate-limited.

Per-Route Overrides

Compute-intensive routes have tighter limits:

json
{
  "Routes": [
    {
      "UpstreamPathTemplate": "/api/v1/invoices/export",
      "UpstreamHttpMethod": ["GET"],
      "DownstreamPathTemplate": "/api/v1/invoices/export",
      "RateLimitOptions": {
        "EnableRateLimiting": true,
        "Period": "1m",
        "Limit": 5,
        "PeriodTimespan": 60
      }
    },
    {
      "UpstreamPathTemplate": "/api/v1/reports/{everything}",
      "UpstreamHttpMethod": ["GET"],
      "DownstreamPathTemplate": "/api/v1/reports/{everything}",
      "RateLimitOptions": {
        "EnableRateLimiting": true,
        "Period": "1m",
        "Limit": 10,
        "PeriodTimespan": 60
      }
    }
  ]
}

Per-Client Limits

Ocelot identifies clients by the ClientId header (injected by the Angular SPA from the Keycloak clientId claim). Different client types have different quotas:

Client TypePeriodLimitUse Case
SPA (Angular)1 minute200 requestsNormal user browsing
Mobile app1 minute100 requestsMobile ERP clients
Integration (API key)1 minute500 requestsThird-party integrations
Export/Report endpoints1 minute5 requestsHeavy computation
Internal services (XApiKey)UnlimitedWhitelisted

429 Response Format

When the rate limit is exceeded, the Gateway returns:

HTTP/1.1 429 Too Many Requests
Retry-After: 42
X-Rate-Limit-Limit: 200
X-Rate-Limit-Remaining: 0
X-Rate-Limit-Reset: 1705313460
Content-Type: application/json

{
  "success": false,
  "data": null,
  "message": "API rate limit exceeded. Please retry after 42 seconds.",
  "errors": null
}
HeaderDescription
Retry-AfterSeconds until the rate limit window resets
X-Rate-Limit-LimitTotal requests allowed in the window
X-Rate-Limit-RemainingRequests remaining in the current window
X-Rate-Limit-ResetUnix timestamp when the window resets

Angular Client Handling

The Angular shared library handles 429 responses with an automatic retry-after delay:

typescript
// libs/shared-lib/src/lib/interceptors/rate-limit.interceptor.ts
@Injectable()
export class RateLimitInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 429) {
          const retryAfter = parseInt(error.headers.get('Retry-After') ?? '60', 10);
          return timer(retryAfter * 1000).pipe(
            switchMap(() => next.handle(req))
          );
        }
        return throwError(() => error);
      })
    );
  }
}

Automatic Retry for User-Initiated Actions

The interceptor only auto-retries requests triggered by user actions (button clicks, navigation). Background polling requests (e.g. notification count refresh) are NOT auto-retried — they are dropped and logged. Auto-retrying background requests would undermine the rate limit.


AFD WAF Rate Limit

The Azure Front Door WAF custom rule limits requests per source IP:

Rule name: RateLimit-Global
Match condition: All requests (no path filter)
Rate limit threshold: 10,000 requests
Rate limit duration: 5 minutes
Action: Block (returns 429)

This protects against volumetric DDoS and prevents a single source IP from overwhelming all tenants. The threshold is set high enough to not affect legitimate power users but low enough to block automated attack tools.


Monitoring Rate Limit Events

Rate limit hits are logged to Seq and Application Insights:

csharp
// Gateway logs rate limit events via Serilog
Log.Warning("Rate limit exceeded for client {ClientId} on route {Route}. " +
            "Limit: {Limit}, Period: {Period}",
    clientId, route, limit, period);

In Application Insights, query for rate limit events:

kusto
requests
| where resultCode == "429"
| summarize count() by bin(timestamp, 5m), name
| order by count_ desc

Alert threshold: if 429 rate exceeds 5% of total requests for any 5-minute window, the DevOps team is notified to investigate whether a single client is misbehaving or whether limits need adjustment.

Internal Documentation — Microtec Platform Team