Rokad

Rate limiting and identity

Build Dhal rate-limit keys from IP, route, user, tenant, and API-key identities.

View repository
dhal documentation
Page 5 of 9

Dhal rate limits can be keyed by more than source IP. This is important for authenticated APIs, multi-tenant systems, and applications where many users share a network address.

Global rate limit

json
{
  "rateLimit": {
    "enabled": true,
    "store": "memory",
    "keyBy": ["ip", "route"],
    "default": {
      "windowSeconds": 60,
      "max": 120
    },
    "routes": {
      "/api/search": {
        "windowSeconds": 60,
        "max": 30
      }
    }
  }
}

Route-limit patterns support exact paths and * wildcards. The most specific matching pattern wins.

Identity keys

Rate limits support these key components:

  • ip
  • route
  • userId
  • tenantId
  • apiKeyId

Credential-stuffing controls additionally support userAgent.

json
{
  "rateLimit": {
    "keyBy": ["tenantId", "userId", "route"]
  }
}

Choose keys that match the abuse boundary. For example:

Use caseSuggested key
Public unauthenticated endpointip, route
Authenticated user actionuserId, route
Tenant-wide quotatenantId, route
API integration quotaapiKeyId, route
Login abuseip, route, optionally userAgent

Identity headers

Dhal resolves identities from request properties first and configured headers second.

json
{
  "identity": {
    "headers": {
      "userId": ["x-dhal-user-id", "x-user-id"],
      "tenantId": ["x-dhal-tenant-id", "x-tenant-id"],
      "apiKeyId": ["x-dhal-api-key-id", "x-api-key-id"]
    }
  }
}

Only trust identity headers that are removed and rewritten by your authentication layer or trusted gateway. Never accept client-supplied identity headers unchanged from the public internet.

Route-level limits

json
{
  "routes": {
    "/api/export": {
      "mode": "block",
      "rateLimit": {
        "enabled": true,
        "windowSeconds": 3600,
        "max": 10,
        "keyBy": ["tenantId", "userId"]
      }
    }
  }
}

A route profile can inherit unspecified values from the global rate-limit configuration.

Memory store

The memory store is appropriate for:

  • local development;
  • tests;
  • a single application process;
  • monitor-only evaluation where exact distributed counts are not required.

It is not a distributed control. Each process maintains independent counters.

Custom stores

A rate-limit store implements:

ts
interface RateLimitStore {
  consume(key: string, limit: {
    windowSeconds: number;
    max: number;
  }): Promise<{
    allowed: boolean;
    remaining: number;
    resetAt: number;
  }>;
}

Pass the store through createDhal() or any framework adapter:

ts
const protection = createDhal({
  configPath: "dhal.json",
  rateLimitStore: myStore
});

For horizontally scaled production deployments, use the provided Redis-compatible store or another shared implementation with equivalent atomicity and expiry behaviour.