You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add AccountLockoutConfig (disabled by default) that locks an account
after N failed password logins for a TTL window. Pluggable
AccountLockoutStore with in-memory and Redis (atomic Lua) backends;
account keys are non-reversible keyed digests. Locked accounts return the
generic bad-credentials response within the existing response-time padding
(non-enumerating, no timing oracle). Complements, not replaces,
IP/identifier rate limiting. Existing apps unaffected unless enabled.
Copy file name to clipboardExpand all lines: docs/configuration/security.md
+94Lines changed: 94 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,6 +14,7 @@ Use this page for CSRF settings, token downgrade policy, schemas, dependency key
14
14
|`verify_minimum_response_seconds`|`0.4`| Minimum wall-clock duration for plugin-owned `POST /auth/verify` success and invalid-token responses. |
15
15
|`request_verify_minimum_response_seconds`|`0.4`| Minimum wall-clock duration for plugin-owned `POST /auth/request-verify-token` responses, including manager failures after rate-limit accounting runs. |
16
16
|`id_parser`|`None`| Parse path/query user ids (e.g. `UUID`). Effective parser: `user_manager_security.id_parser` when set; otherwise `LitestarAuthConfig.id_parser` is applied (including the no-`user_manager_security` default builder path). |
17
+
|`account_lockout_config`|`AccountLockoutConfig()`| Opt-in per-account password-login lockout. Disabled by default; when enabled it uses `failure_threshold=5`, `window_seconds=900.0`, and an in-memory store unless `store_factory` supplies a shared store. |
17
18
18
19
### CSRF cookie name and login telemetry
19
20
@@ -28,6 +29,99 @@ Set optional `UserManagerSecurity.login_identifier_telemetry_secret` so structur
28
29
an HMAC digest of the identifier on failures (no raw identifier in logs). Details:
|`failure_threshold`|`5`| Number of failed password checks for the same account key before the key is locked. |
45
+
|`window_seconds`|`900.0`| Counter TTL and lockout window in seconds. A successful password login before the threshold resets the counter. |
46
+
|`store_factory`|`None`| Optional zero-argument factory returning an `AccountLockoutStore`. When omitted, the config memoizes an `InMemoryAccountLockoutStore`. |
47
+
48
+
Plugin-managed lockout key derivation uses
49
+
`UserManagerSecurity.login_identifier_telemetry_secret`. Set a high-entropy, role-separated value
50
+
before enabling lockout:
51
+
52
+
```python
53
+
from litestar_auth import LitestarAuthConfig, UserManagerSecurity
54
+
from litestar_auth.ratelimit import AccountLockoutConfig
Copy file name to clipboardExpand all lines: docs/security.md
+14-2Lines changed: 14 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -37,6 +37,12 @@ Before production rollout, verify the deployment-owned security contract in
37
37
-**Failed-login telemetry** — failed-login logs never include the submitted email/username. Configure
38
38
`UserManagerSecurity.login_identifier_telemetry_secret` when you want a stable, non-reversible
39
39
`identifier_digest`; the digest is omitted when that dedicated secret is unset.
40
+
-**Per-account lockout** — optional password-login brute-force protection through
41
+
`AccountLockoutConfig`. It is disabled by default; when enabled, login identifiers are normalized
42
+
and stored only as keyed digests, repeated failed password checks lock that account key for the
43
+
configured window, and a successful password login before lockout resets the counter. Locked
44
+
accounts deliberately return the same `LOGIN_BAD_CREDENTIALS` response as wrong credentials or a
45
+
missing user, without a distinct lockout code, status, or `Retry-After` header.
40
46
-**Rate limiting** — optional per-endpoint limits; in-memory backend is single-process only and fails closed for new keys when its capacity cap is reached.
41
47
-**Route-level role checks** — `is_superuser`, `has_any_role(...)`, and `has_all_roles(...)` reuse the same normalized flat-role semantics as persistence and manager writes, and they fail closed if the authenticated user does not expose the documented role-capable contract. Role guard matching uses fixed-work internal comparisons over normalized role strings to avoid role-membership short-circuit predicates; this is a defense-in-depth posture, not a claim of cryptographic constant-time behavior across Python or the network.
42
48
@@ -64,8 +70,9 @@ When you assemble `JWTStrategy` or `BaseUserManager` yourself, inspect the runti
64
70
`TOTP_STEPUP_REQUIRED` 403 contract, default endpoint policies, recovery-code behavior, and the
65
71
API-key transport rationale.
66
72
-`BaseUserManager` uses `UserManagerSecurity.login_identifier_telemetry_secret` only for
67
-
failed-login identifier digests. It is optional; omitting it keeps logs correlation-safe by
68
-
leaving `identifier_digest` out rather than reusing another auth secret.
73
+
failed-login identifier digests and plugin-managed account-lockout key derivation. It is optional
74
+
while lockout is disabled; enabling `AccountLockoutConfig` without this secret is rejected. Omit it
75
+
only when you do not need correlated failed-login telemetry and do not enable account lockout.
69
76
70
77
## OAuth redirect-host DNS validation
71
78
@@ -124,6 +131,7 @@ Additional opt-ins to weaker behavior:
124
131
|`CookieTransport(allow_insecure_cookie_auth=True)`| Allows cookie auth without CSRF for controlled non-browser scenarios only. |
125
132
|`ApiKeyConfig(signing_enabled=True, secret_encryption_keyring=...)`| Enables request signing, but stores an encrypted copy of signing-required key secrets so signatures can be verified. |
126
133
|`InMemoryApiKeyNonceStore`| Process-local API-key signing replay cache; use `RedisApiKeyNonceStore` for multi-worker deployments. |
134
+
|`AccountLockoutConfig(enabled=True)` with the default in-memory store | Process-local per-account lockout counters; use `RedisAccountLockoutStore` for multi-worker deployments. |
127
135
128
136
## Bearer failure-code taxonomy
129
137
@@ -157,6 +165,10 @@ expired, timestamp-skewed, or nonce-replayed keys, still consume the limiter bef
157
165
- No built-in **email** sending — you must implement hooks.
158
166
- No **RBAC** or **WebAuthn** in core — the built-in role guards are flat membership checks only; extend in your application for permission matrices or object-level policy.
159
167
-**Durable JWT revocation** requires a shared store — `JWTStrategy(secret=...)` without `denylist_store` or `allow_inmemory_denylist=True` fails closed at construction time. Use Redis (or equivalent) denylist for multi-worker production if you rely on revoke; reserve `allow_inmemory_denylist=True` for single-process development or tests.
168
+
-**Per-account lockout** requires a shared store in multi-worker deployments. The default
169
+
`InMemoryAccountLockoutStore` is single-process only and is a development/single-worker tool; use
170
+
`RedisAccountLockoutStore` or a custom shared `AccountLockoutStore` when multiple workers can
0 commit comments