> ## Documentation Index
> Fetch the complete documentation index at: https://nestcrate.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Secure your nestrs API

> Configure security headers, CORS, rate limiting, CSRF, cookies, and auth guards to harden your nestrs API. Includes a pre-production security checklist.

nestrs exposes security controls as explicit opt-in builder calls on `NestApplication`. Nothing is enabled by default — small services that don't need a particular control don't pay for it. This page covers each control in order of how frequently you'll need it, followed by a checklist you can run through before shipping.

## Security headers

Call `use_security_headers` with a `SecurityHeaders` value to inject protective HTTP headers on every response. `SecurityHeaders::default()` sets the most broadly applicable headers:

```rust theme={null}
use nestrs::prelude::*;

NestFactory::create::<AppModule>()
    .use_security_headers(SecurityHeaders::default())
    .listen(3000)
    .await;
```

The defaults set:

* `X-Content-Type-Options: nosniff`
* `X-Frame-Options: DENY`
* `Referrer-Policy: strict-origin-when-cross-origin`
* `X-XSS-Protection: 0`
* `Permissions-Policy: geolocation=(), microphone=(), camera=()`

For browser-facing APIs that need Helmet-style hardening, use `SecurityHeaders::helmet_like()`, which adds `Cross-Origin-Opener-Policy`, `Cross-Origin-Resource-Policy`, `X-DNS-Prefetch-Control`, `X-Download-Options`, and `X-Permitted-Cross-Domain-Policies` on top of the defaults:

```rust theme={null}
NestFactory::create::<AppModule>()
    .use_security_headers(
        SecurityHeaders::helmet_like()
            .content_security_policy("default-src 'self'")
            .hsts("max-age=63072000; includeSubDomains"),
    )
    .listen(3000)
    .await;
```

<Note>
  `helmet_like()` does **not** set CSP or HSTS automatically — configure both explicitly for your deployment. nestrs runs behind a reverse proxy in most production topologies; HSTS may already be set at the edge.
</Note>

## CORS

CORS is off until you call `enable_cors`. Pass a `CorsOptions` value with an explicit origin allowlist for browser clients:

```rust theme={null}
use nestrs::prelude::*;

NestFactory::create::<AppModule>()
    .enable_cors(
        CorsOptions::builder()
            .allow_origins(["https://app.example.com"])
            .allow_methods(["GET", "POST", "PUT", "DELETE"])
            .allow_headers(["content-type", "authorization"])
            .allow_credentials(true)
            .max_age_secs(600)
            .build(),
    )
    .listen(3000)
    .await;
```

For local development only, `CorsOptions::permissive()` allows all origins. nestrs emits a `tracing` WARN at startup if you use permissive CORS when `NESTRS_ENV`, `APP_ENV`, or `RUST_ENV` is set to `production`.

<Warning>
  You cannot combine `allow_credentials(true)` with a wildcard `*` origin — browsers reject it. Always set an explicit list when you need credentialed cross-origin requests.
</Warning>

## Rate limiting

`use_rate_limit` accepts a `RateLimitOptions` value. The defaults allow 100 requests per 60-second window per client IP:

```rust theme={null}
NestFactory::create::<AppModule>()
    .use_rate_limit(
        RateLimitOptions::builder()
            .max_requests(200)
            .window_secs(60)
            .build(),
    )
    .listen(3000)
    .await;
```

For shared rate limits across multiple instances, enable the `cache-redis` feature and call `.redis(url, key_prefix)`:

```rust theme={null}
RateLimitOptions::builder()
    .max_requests(500)
    .window_secs(60)
    .redis("redis://127.0.0.1:6379", "rl:")
    .build()
```

## Route-level throttling

Where the global rate limiter applies one budget to everything, `use_throttler` applies per-route budgets that individual handlers declare with decorators:

```rust theme={null}
NestFactory::create::<AppModule>()
    .use_throttler(ThrottlerOptions {
        global: ThrottleSpec::parse("100/minute"), // fallback for undecorated routes
        ..ThrottlerOptions::default()
    })
    .listen(3000)
    .await;
```

Per route:

```rust theme={null}
#[routes(state = AppState)]
impl UploadController {
    #[post("/avatar")]
    #[throttle(5, "minute")]        // 5 uploads per client per minute
    async fn avatar(/* … */) { /* … */ }

    #[get("/status")]
    #[skip_throttle]                // exempt entirely
    async fn status() -> &'static str { "ok" }
}
```

* `#[throttle(n, "second" | "minute" | "hour")]` overrides the global spec for that route; rejected requests get `429` with `Retry-After` and `X-RateLimit-Remaining` headers.
* `#[skip_throttle]` exempts a route entirely.
* `ThrottlerOptions.global` is the fallback for undecorated routes — `None` means only explicitly decorated routes are throttled.
* Like the rate limiter, the throttler supports a shared Redis backend (`cache-redis` feature) and inherits the trusted-proxy hop count (below).

## Proxy topology and client identity

Both the rate limiter and the throttler key on the **client IP**, and the `ClientIp` extractor exposes it to handlers. Behind a reverse proxy or load balancer, the socket address is the proxy's — so client identity has to come from `X-Forwarded-For` / `X-Real-IP`. Those headers are attacker-spoofable, so nestrs never trusts them unless you declare your topology:

```rust theme={null}
NestFactory::create::<AppModule>()
    .use_trusted_proxy_headers(1)   // exactly one trusted proxy in front
    .use_rate_limit(RateLimitOptions::builder().max_requests(200).window_secs(60).build())
    .listen(3000)
    .await;
```

* `use_trusted_proxy_headers(hops)` declares how many trusted proxies sit in front. The client address is read **right-most-first**: with the common `client → LB → app` topology, the entry appended by your load balancer is used and any attacker-supplied prefix is ignored.
* Forwarded headers are **never** consulted without this setting — trusting them by default would allow trivial IP-based rate-limit bypass.
* The rate limiter and throttler **inherit** the hop count automatically, so they key on the same identity as `ClientIp`. Divergent per-component overrides are logged as a `tracing` WARN.
* Common topologies: direct exposure → omit the call (default 0 hops); one load balancer → `1`; LB + CDN → `2` (count only hops you control and trust).

## CSRF protection

CSRF protection targets cookie-based browser flows. Bearer token APIs in `Authorization` headers are not CSRF-bound and do not need this.

<Steps>
  <Step title="Enable the csrf feature">
    ```toml theme={null}
    [dependencies]
    nestrs = { version = "1.6.0", features = ["csrf", "cookies"] }
    ```
  </Step>

  <Step title="Enable cookies and CSRF middleware">
    ```rust theme={null}
    use nestrs::prelude::*;

    NestFactory::create::<AppModule>()
        .use_cookies()
        .use_csrf_protection(CsrfProtectionConfig::default())
        .listen(3000)
        .await;
    ```

    `CsrfProtectionConfig` uses a double-submit pattern: your app sets a cookie on safe requests, and the client must echo the same value in an `X-CSRF-Token` header on POST/PUT/PATCH/DELETE.
  </Step>
</Steps>

If you enable `use_cookies()` or `use_session_memory()` without wiring `use_csrf_protection`, nestrs emits a `tracing` WARN at router build time. Treat this as a release blocker for any browser-facing endpoint that mutates state.

## Cookies and sessions

<Tabs>
  <Tab title="Cookies only">
    ```rust theme={null}
    NestFactory::create::<AppModule>()
        .use_cookies()   // feature: cookies
        .listen(3000)
        .await;
    ```
  </Tab>

  <Tab title="In-memory sessions">
    ```rust theme={null}
    NestFactory::create::<AppModule>()
        .use_session_memory()   // feature: session — implies cookies
        .listen(3000)
        .await;
    ```
  </Tab>
</Tabs>

Always pair cookie or session middleware with `use_csrf_protection` for any endpoint that accepts browser-originated mutations.

## Guards and authentication

For token validation you implement `CanActivate` (HTTP guards) or `AuthStrategy` (credential-validation strategies) and compose them on controllers or individual routes. For full OAuth2 — client flows, JWKS-based resource-server validation, social providers — see the [OAuth2 guide](/guides/oauth2); for per-row authorization see the [authorization guide](/guides/authorization).

### CanActivate guard

```rust theme={null}
use nestrs::prelude::*;
use axum::http::request::Parts;

pub struct AuthGuard;

#[async_trait]
impl CanActivate for AuthGuard {
    async fn can_activate(&self, parts: &Parts) -> Result<(), GuardError> {
        let token = parts
            .headers
            .get("authorization")
            .and_then(|v| v.to_str().ok())
            .and_then(|v| parse_authorization_bearer(v))
            .ok_or_else(|| GuardError::unauthorized("missing token"))?;

        validate_token(token)
            .map_err(|_| GuardError::unauthorized("invalid token"))
    }
}

#[controller(prefix = "/users")]
#[use_guards(AuthGuard)]
pub struct UsersController;
```

### BearerToken extractor

For routes that unconditionally require a bearer token, use the `BearerToken` extractor directly — it returns `401` when the header is absent or malformed:

```rust theme={null}
#[routes(state = AppState)]
impl MeController {
    #[get("/me")]
    pub async fn me(BearerToken(token): BearerToken) -> String {
        token
    }
}
```

Use `OptionalBearerToken` when the header is optional:

```rust theme={null}
pub async fn maybe_authed(OptionalBearerToken(token): OptionalBearerToken) -> String {
    token.unwrap_or_else(|| "anonymous".into())
}
```

### AuthStrategyGuard

`AuthStrategyGuard<S>` wraps any type that implements `AuthStrategy` and can be derived as `Default`. Wire it onto a controller or individual route with `#[use_guards]`:

```rust theme={null}
pub struct JwtStrategy;

impl Default for JwtStrategy {
    fn default() -> Self { Self }
}

#[async_trait]
impl AuthStrategy for JwtStrategy {
    async fn validate(&self, parts: &Parts) -> Result<(), AuthError> {
        // verify JWT from Authorization header
        todo!()
    }
}

#[controller(prefix = "/admin")]
#[use_guards(AuthStrategyGuard<JwtStrategy>)]
pub struct AdminController;
```

## Body limits and timeouts

Set a maximum request body size and a per-request timeout for any public endpoint:

```rust theme={null}
use std::time::Duration;

NestFactory::create::<AppModule>()
    .use_body_limit(1 * 1024 * 1024)          // 1 MiB
    .use_request_timeout(Duration::from_secs(10))
    .listen(3000)
    .await;
```

## Production error sanitization

5xx JSON bodies are **sanitized by default whenever the runtime environment is production** — the first non-empty value of `NESTRS_ENV`, `APP_ENV`, or `RUST_ENV` is compared (case-insensitively) against `production` / `prod`. Sanitization replaces the internal `message` with `"An unexpected error occurred"`, drops any `errors` payload, and inserts the canonical reason phrase, so stack traces and internal detail never reach clients. No builder call is needed in production; `enable_production_errors_from_env()` re-reads the environment (kept for explicitness), and `enable_production_errors()` forces sanitization on in any environment — useful for staging that runs with `NESTRS_ENV=staging`:

```rust theme={null}
NestFactory::create::<AppModule>()
    .enable_production_errors()   // force on regardless of environment
    .listen(3000)
    .await;
```

`disable_production_errors()` opts out even in production — for internal admin services behind a trusted boundary that want detailed errors.

## Pre-production checklist

Run through this list before deploying a browser-facing or multi-tenant API:

<AccordionGroup>
  <Accordion title="Headers and CORS">
    * `use_security_headers(SecurityHeaders::default())` is called, or `helmet_like()` for richer isolation.
    * CSP is set explicitly for HTML-serving endpoints.
    * `enable_cors(...)` uses an explicit origin allowlist — not `CorsOptions::permissive()`.
    * `allow_credentials(true)` is not combined with a wildcard origin.
  </Accordion>

  <Accordion title="CSRF and cookies">
    * Cookie or session flows have `use_csrf_protection(...)` wired.
    * The `csrf` Cargo feature is enabled alongside `cookies`.
    * No `tracing` WARN about missing CSRF at startup.
  </Accordion>

  <Accordion title="Rate limiting and timeouts">
    * `use_rate_limit(...)` is configured (or enforced at the edge).
    * `use_throttler(...)` covers expensive routes with tighter `#[throttle]` budgets where needed.
    * `use_body_limit(...)` is set per endpoint class.
    * `use_request_timeout(...)` is set for public routes.
  </Accordion>

  <Accordion title="Proxy topology">
    * `use_trusted_proxy_headers(hops)` matches the real proxy chain — 0 if directly exposed.
    * No component overrides `trusted_proxy_hops` to a divergent value (WARN at startup if so).
  </Accordion>

  <Accordion title="Errors and dependencies">
    * 5xx sanitization is active (default in production; `disable_production_errors()` only behind a trusted boundary).
    * `cargo audit` is passing locally and in CI.
    * Secrets are loaded from env / secret manager, not committed to source.
    * Logs do not contain tokens, passwords, or API keys.
  </Accordion>
</AccordionGroup>

The defaults for every control are listed in `docs/src/secure-defaults.md` (in the repository), which also includes the full secure-by-default matrix.
