---
title: "Production deployment requirements"
manual: "Passkeys Backend Authentication"
version: "1.0"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:security-deployment@1.0"
source: "Security/Deployment.rst"
rendered: "2026-09-20T16:07:13+00:00"
---

# Production deployment requirements {#security-deployment}

The extension's security mechanisms depend on certain TYPO3
and server configurations being set correctly. Review each
section below before deploying to production.

## Trusted hosts pattern {#security-trusted-hosts}

When [rpId](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-rpid@1.0) and [origin](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-origin@1.0) are left empty
(the default), the extension auto-detects them from the
`HTTP_HOST` server variable. An attacker who can inject an
arbitrary `Host` header could cause the extension to
generate challenge tokens bound to a malicious origin.

TYPO3 mitigates this with the `trustedHostsPattern`
setting, but the default value `.*` allows **any** host
header.

> [!WARNING]
> You **must** configure `trustedHostsPattern` in
> production. Leaving it at the default `.*` disables
> host header validation entirely.

**config/system/settings.php**

```php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern']
    = '(^|\.)example\.com$';
```

Alternatively, set [rpId](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-rpid@1.0) and [origin](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-origin@1.0)
explicitly in the extension configuration. This bypasses
auto-detection entirely and removes the dependency on host
header validation for passkey operations.

## Reverse proxy and IP detection {#security-reverse-proxy}

Rate limiting and account lockout use the client's IP address
(via `GeneralUtility::getIndpEnv('REMOTE_ADDR')`). Behind
a reverse proxy, all requests appear to originate from the
proxy's IP address unless TYPO3 is configured to read the
real client IP from forwarded headers.

Without this configuration:

-   **Rate limiting becomes ineffective** -- all clients share
    a single counter and hit the limit collectively.
-   **Account lockout affects all users** -- one locked account
    blocks authentication for every user behind the same
    proxy.

Configure TYPO3 to trust your reverse proxy:

**config/system/settings.php**

```php
// IP address(es) of your reverse proxy (comma-separated)
$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP']
    = '10.0.0.1,10.0.0.2';

// Use the last (rightmost) value in X-Forwarded-For
$GLOBALS['TYPO3_CONF_VARS']['SYS']
    ['reverseProxyHeaderMultiValue'] = 'last';
```

> [!TIP]
> If you use a CDN or cloud load balancer (e.g. AWS ALB,
> Cloudflare), ensure the `X-Forwarded-For` header chain
> is properly configured and that TYPO3's
> `reverseProxyIP` matches the load balancer's egress IP
> range.

## Multi-server cache backends {#security-multi-server-caching}

The extension uses two TYPO3 caches:

-   `nr_passkeys_be_nonce` -- Stores single-use nonces for
    challenge replay protection and single-use login tokens
    (default backend: `FileBackend`).
-   `nr_passkeys_be_ratelimit` -- Stores rate-limit counters
    and lockout flags (default backend: `FileBackend`).

File-based cache backends store data on the local filesystem.
In a **multi-server deployment** (multiple TYPO3 instances
behind a load balancer), each server maintains its own
independent cache. This has two consequences:

1.  **Nonce replay across servers** -- A challenge token
    consumed on server A still exists in server B's cache,
    allowing a replayed token to pass verification on
    server B.
1.  **Rate-limit bypass** -- An attacker can distribute
    requests across servers, with each server tracking only
    a fraction of the total attempts.

For multi-server deployments, configure a shared cache
backend:

**config/system/additional.php**

```php
// Use Redis for nonce cache
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']
    ['cacheConfigurations']['nr_passkeys_be_nonce']
    ['backend']
    = \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']
    ['cacheConfigurations']['nr_passkeys_be_nonce']
    ['options'] = [
        'database' => 3,
        'defaultLifetime' => 300,
        // 'hostname' => '127.0.0.1',
        // 'port' => 6379,
        // 'password' => '',
    ];

// Use Redis for rate-limit cache
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']
    ['cacheConfigurations']['nr_passkeys_be_ratelimit']
    ['backend']
    = \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']
    ['cacheConfigurations']['nr_passkeys_be_ratelimit']
    ['options'] = [
        'database' => 4,
        'defaultLifetime' => 600,
        // 'hostname' => '127.0.0.1',
        // 'port' => 6379,
        // 'password' => '',
    ];
```

> [!NOTE]
> Single-server deployments (including DDEV and most
> small-to-medium installations) work correctly with the
> default file-based backends. This only applies when
> multiple application servers share the same domain.
