---
title: "Security Policies"
url: "https://effect-auth.itsbroly.com/concepts/security-policies/"
description: "Configure secure defaults and operation-specific overrides."
---



`AuthRateLimit` is the HTTP-operation security seam for effect-auth. Its standard implementation applies operation-specific rate limits before authentication work begins. It is intentionally narrower than application authorization: it limits attempts using trusted request or session context, but it does not decide whether an actor may access your tenant, billing account, admin screen, or other application resource.

```text
HTTP request
  -> origin/CSRF and application boundary guards
  -> secured *HttpOperation
       -> AuthRateLimit.require({ operation, ip/email/user }) exactly once
       -> authentication domain service
  -> error mapping / cookie commitment
```

## Standard policy [#standard-policy]

Provide `AuthRateLimitStandardLive()` to the HTTP Operations layer. The operation implementation captures that service and calls its own policy; the preset API and a [Custom API](/guides/custom-auth-api/) merely bind the same operation. Do not call `AuthRateLimit.require` again around a standard operation: doing so consumes rate-limit budget twice. Calls made directly to primitives do not pass through this seam, so the application owns equivalent boundary controls. See [Architecture](/concepts/architecture/) for the layer boundaries.

```ts
const OperationsLive = PasswordHttpOperationsLive.pipe(
  Layer.provide(AuthRateLimitStandardLive()),
  Layer.provide(AppAuthServicesLive)
);
```

Defaults are fixed-window rules. When an operation has two rows, both rules are combined into one `RateLimitPolicy` and required in one `AuthRateLimit` call.

| Operations                                                 | Key       | Limit / window              |
| ---------------------------------------------------------- | --------- | --------------------------- |
| Password sign-in                                           | IP; email | 20 / 10m; 5 / 10m           |
| Password sign-up                                           | IP; email | 10 / 1h; 3 / 1h             |
| Password reset start                                       | IP; email | 10 / 10m; 3 / 10m           |
| Password reset verify                                      | IP        | 20 / 10m                    |
| Email verification start                                   | IP; email | 10 / 10m; 3 / 10m           |
| Email OTP start                                            | IP; email | 10 / 10m; 5 / 10m           |
| Email OTP verify                                           | IP        | 20 / 10m                    |
| Email auth start, magic-link start                         | IP; email | 10 / 10m; 5 / 10m           |
| Magic-link verify                                          | IP        | 20 / 10m                    |
| Passkey registration start / finish                        | user      | 10 / 1h; 30 / 10m           |
| Passkey authentication start / finish                      | IP        | 20 / 10m; 30 / 10m          |
| Passkey credential list / revoke                           | user      | 60 / 1m; 20 / 10m           |
| TOTP enrollment start / confirm, verify                    | user      | 10 / 1h; 20 / 10m; 20 / 10m |
| TOTP factor list / revoke                                  | user      | 60 / 1m; 20 / 10m           |
| Recovery-code generate / regenerate                        | user      | 10 / 1h each                |
| Recovery-code verify / list / revoke                       | user      | 20 / 10m; 60 / 1m; 20 / 10m |
| MFA options                                                | IP        | 30 / 10m                    |
| MFA TOTP and recovery-code verify, including flow variants | IP        | 20 / 10m each               |
| MFA passkey start / verify                                 | IP        | 30 / 10m each               |
| Step-up options                                            | user      | 60 / 1m                     |
| Step-up password, TOTP, recovery-code verify               | user      | 20 / 10m each               |
| Step-up passkey start / verify                             | user      | 30 / 10m each               |

The operation names and rule IDs are exported as `AuthRateLimitStandardRules`; use the exact operation keys shown there when configuring overrides.

## Overrides and disabling [#overrides-and-disabling]

Configuration is per operation. An array **replaces** that operation's defaults rather than stacking with them. `null` disables policy for that operation. An omitted key (`undefined`) retains the standard rules. Use `null` only when another control deliberately owns the risk; `AuthRateLimitNoopLive` disables the entire service and is primarily useful for explicit replacement or tests.

```ts
AuthRateLimitStandardLive({
  "auth.password.sign_in": [
    {
      id: "app.password.sign_in.ip",
      key: "ip",
      limit: 8,
      window: Duration.minutes(15),
    },
  ],
  "auth.magic_link.verify": null,
});
```

Rules may select `fixed-window` (the default) or `token-bucket`, with `tokens` where appropriate. Email- and user-keyed rules require the operation to supply that subject; a missing subject fails closed with `RateLimitStoreError`. A missing IP uses the stable `ip:missing` bucket so requests without trusted IP metadata are still constrained. Rate-limit-store failures, exceeded limits, and privacy hashing failures propagate as typed errors and are mapped by HTTP Operations; the implementation does not silently fail open.

## Privacy and keys [#privacy-and-keys]

Raw email addresses and IP addresses are not used as limiter storage keys. `PrivacyLive` normalizes them and computes namespaced HMAC-SHA-256 hashes using the application privacy secret; `RateLimitKey.emailHash` and `RateLimitKey.ipHash` then identify the key kind. User rules use the internal `UserId`, not browser-supplied input. Configure proxy-header trust carefully so an attacker cannot choose the IP metadata used by policy.

`RateLimiter` owns policy composition and consumption. Its live implementation delegates persistence to Effect's persistence rate limiter, while `RateLimiterMemoryLive` is process-local. Choose durable storage when counters must survive process or isolate replacement.

## Origin, CSRF, and CORS [#origin-csrf-and-cors]

`AuthOriginCheckMiddleware` is a request-side CSRF defense for unsafe methods (`POST`, `PUT`, `PATCH`, and `DELETE`). It accepts same-origin requests or normalized origins in `allowedOrigins`, using `Origin` and then `Referer`. Missing headers are allowed by default for non-browser clients; set `allowMissingOrigin: false` for strict browser-only boundaries. This check is separate from `AuthRateLimit` and separate from the double-submit `AuthCsrfMiddleware` primitive.

Origin/CSRF checks are not CORS. CORS controls which cross-origin responses browsers expose and which preflights succeed; configure its response headers independently, especially when credentials are used.

Finally, keep application authorization outside `AuthRateLimit`. Session, role, tenant, ownership, recent-step-up, and business-resource decisions belong in [App-owned Guards](/guides/app-owned-guards/) or domain invariants. If you build directly from primitives, you also own transport validation, origin/CSRF policy, rate limiting, error projection, auditing, and cookie/session commitment that the HTTP Operations layer would otherwise provide.

