Authority Rate Limit Guidance
Audience: Authority operators, SOC, Security Guild. Scope: Fixed-window rate limiting on the
/token,/authorize, and/internal/*routes — recommended defaults, override scenarios, partitioning behaviour, and monitoring practices.
Stella Ops Authority applies fixed-window rate limiting to critical endpoints so that brute-force and burst traffic are throttled before they can exhaust downstream resources. This guide complements the Standard-plugin credential lockout controls (see etc/authority.plugins/standard.yaml → lockout, and the Authority plugin developer guide) and the password-hashing guidance.
The limiter is wired in Program.cs via AddRateLimiter(... AuthorityRateLimiter.Configure(...)) and runs as a single ASP.NET Core GlobalLimiter (AuthorityRateLimiter.CreateGlobalLimiter). Inside that global limiter each of the three endpoint groups gets its own fixed-window partition; the three groups are identified internally by the partition-key prefixes authority-token, authority-authorize, and authority-internal (AuthorityRateLimiter.TokenLimiterName/AuthorizeLimiterName/InternalLimiterName). They are not registered as named ASP.NET Core rate-limit policies, so the built-in limiter metric dimension is not populated from a policy name (see Monitoring and Alerts). Rejected requests return HTTP 429 Too Many Requests (RejectionStatusCode).
Configuration Overview
Rate limits live under security.rateLimiting in authority.yaml (bound to AuthorityRateLimitingOptions → AuthorityEndpointRateLimitOptions). Each endpoint exposes:
enabled— toggles the limiter. Defaults totruefortoken/authorizeandfalseforinternal.permitLimit— maximum requests per fixed window. Must be greater than zero when enabled.window— window duration expressed as ahh:mm:sstimespan (e.g.,00:01:00). Must be greater than zero and no longer than one hour (Window > TimeSpan.FromHours(1)fails validation).queueLimit— number of requests allowed to queue when the window is exhausted. Must be zero or greater.queueProcessingOrder— ordering for queued requests; defaults toOldestFirst. (Configurable in code but not surfaced in the sample YAML.)
security:
rateLimiting:
token:
enabled: true
permitLimit: 30
window: 00:01:00
queueLimit: 0
authorize:
enabled: true
permitLimit: 60
window: 00:01:00
queueLimit: 10
internal:
enabled: false
permitLimit: 5
window: 00:01:00
queueLimit: 0
The shipped
etc/authority.yamldefines only thetokenandauthorizeblocks; theinternallimiter is disabled by default (its in-code defaults arepermitLimit: 5,window: 00:01:00,queueLimit: 0). Add theinternalblock only when you need to throttle the/internal/*bootstrap routes.
When a request is throttled, AuthorityRateLimiter.Configure’s OnRejected callback writes a Retry-After response header (ceiling of the lease’s retry-after in whole seconds) and logs a warning with the request path and remote IP. Before the limiter runs, AuthorityRateLimiterMetadataMiddleware captures structured tags on the request for correlation. The tags it sets are:
authority.endpoint— normalised request path (always set).authority.remote_ip— resolved client IP, falling back tounknown(always set).authority.captured_at— ISO-8601 capture timestamp (always set).authority.client_id— resolved OAuth client id, set only on/token(from Basic auth header or formclient_id) and/authorize(from theclient_idquery parameter).authority.forwarded_for— proxy-supplied client IP (X-Forwarded-For,X-Real-IP, orForwarded), set only when present.authority.user_agent— requestUser-Agent, set only when present.
Environment overrides bind through StellaOpsAuthorityConfiguration (EnvironmentPrefix = "STELLAOPS_AUTHORITY_", BindingSection = "Authority"). Because the environment-variable provider strips the prefix and binds the Authority section using __ as the hierarchy separator, the key must retain the Authority section segment after the prefix. For example:
STELLAOPS_AUTHORITY_AUTHORITY__SECURITY__RATELIMITING__TOKEN__PERMITLIMIT=60
STELLAOPS_AUTHORITY_AUTHORITY__SECURITY__RATELIMITING__TOKEN__WINDOW=00:01:00
Recommended Profiles
The values below are the token limiter defaults and suggested overrides. The shipped default (AuthorityRateLimitingOptions) is token = permitLimit 30 / window 00:01:00 / queueLimit 0 and authorize = permitLimit 60 / window 00:01:00 / queueLimit 10. Note that window is capped at one hour by validation, so the “Incident lockdown” example below uses 00:05:00 (300s), well within the limit.
| Scenario | permitLimit | window | queueLimit | Notes |
|---|---|---|---|---|
| Default production | 30 | 60s | 0 | Matches the shipped token default; leaves headroom for tenant bursts. |
| High-trust clustered IPs | 60 | 60s | 5 | Requires WAF allowlist + an alert when authority rate-limit rejections stay above ~1% sustained (see Monitoring and Alerts). |
| Air-gapped lab | 10 | 120s | 0 | Lower concurrency reduces noise when running from shared bastion hosts. |
| Incident lockdown | 5 | 300s | 0 | Pair with a reduced Standard-plugin lockout.maxAttempts (default 5) and SOC paging for each denial. |
Partitioning behaviour
DefaultAuthorityRateLimiterPartitionKeyResolver builds the per-window partition key as follows:
/internal/*and the fallback path: partitioned by{limiterName}:{remoteIp}(remote IP only)./tokenand/authorize: partitioned by{limiterName}:{remoteIp}:{clientId}when aclient_idwas resolved, otherwise by{limiterName}:{remoteIp}.
This means two different client_id values from the same IP get independent token-window budgets (verified by AuthorityRateLimiterIntegrationTests.TokenEndpoint_AllowsDifferentClientIdsWithinWindow).
Lockout Interplay
- Rate limiting throttles by IP (and by IP +
client_idon/token//authorize); credential lockout applies per subject in the authenticating plugin. Keep both enabled. - During lockdown scenarios, reduce the Standard plugin’s
lockout.maxAttempts(inetc/authority.plugins/standard.yaml; default5, withlockout.windowMinutesdefault15) alongside the rate limits above so that subjects face quicker escalation. There is nosecurity.lockout.maxFailuresknob in Authority core configuration — lockout is owned by the credential plugin, notauthority.yaml. - Map support playbooks to the observed
Retry-Aftervalue: anything above 120 seconds should trigger manual investigation before re-enabling clients.
Monitoring and Alerts
- Metrics
aspnetcore_rate_limiting_rejections_total— the ASP.NET Core rate-limiter rejection counter. Note: because Authority uses a singleGlobalLimiterrather than named policies, thelimiterlabel is not populated withauthority-token/authority-authorizefrom a policy name. Theauthority-*strings are internal partition-key prefixes, not exported as the meter’slimiterdimension. Filter or split byservice_name="stellaops-authority"and correlate the specific endpoint via the structured log tags below rather than relying on a per-endpointlimiterlabel. (Existing dashboards underdocs/modules/authority/operations/use the{limiter=...}form for forward compatibility; treat that series as empty until named policies are introduced.)- Custom counters/dashboards derived from the structured log tags emitted by
AuthorityRateLimiterMetadataMiddleware(authority.endpoint,authority.remote_ip,authority.client_id,authority.forwarded_for). These are the authoritative per-endpoint, per-client correlation signals.
- Dashboards
- Requests vs. rejections per endpoint.
- Top offending clients/IP ranges in the current window.
- Heatmap of retry-after durations to spot persistent throttling.
- Alerts
- Notify SOC when 429 rates exceed 25 % for five consecutive minutes on any limiter.
- Trigger client-specific alerts when a single client_id produces >100 throttle events/hour.
Operational Checklist
- Validate updated limits in staging before production rollout; smoke-test with representative workload.
- When raising limits, confirm audit events continue to capture
authority.client_id,authority.remote_ip, and correlation IDs for throttle responses. - Document any overrides in the change log, including justification and expiry review date.
