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

# Deployment scenarios {#deployment-scenarios}

Passkeys are bound to a specific domain (the **Relying Party
ID**). This chapter explains how to configure the extension
across different environments and how to handle common
deployment patterns.

**On this page**

-   [Single environment](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:single-environment@1.0)
-   [Multi-environment (local / staging / production)](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:multi-environment-local-staging-production@1.0)
-   [Shared rpId across subdomains](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:shared-rpid-across-subdomains@1.0)

## Single environment {#single-environment}

The simplest setup: one TYPO3 instance with one domain.

Leave [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) empty (the
default). The extension auto-detects both values from the
incoming HTTP request. Each passkey is registered against
the domain it was created on.

This works for:

-   A single production instance (e.g. `cms.example.com`)
-   A local DDEV site (e.g. `mysite.ddev.site`)

No additional configuration is needed.

## Multi-environment (local / staging / production) {#multi-environment-local-staging-production}

A typical setup has three environments:

-   **Local development**: `mysite.ddev.site`
    (or `mysite.local`)
-   **Staging**: `staging.example.com`
-   **Production**: `www.example.com`

### Recommended: separate passkeys per environment {#deployment-rpid-per-environment}

Leave [rpId](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-rpid@1.0) empty on all environments. Each
environment auto-detects its own domain, so passkeys are
environment-specific. Users register a separate passkey on
each environment they need access to.

Modern authenticators (iCloud Keychain, Windows Hello,
1Password, YubiKey) make registering on multiple
environments trivial -- it takes about 10 seconds per
environment.

> [!TIP]
> This is the recommended approach. It avoids sharing
> secrets across environments and keeps each environment
> fully independent.

### Environment-specific configuration {#deployment-context-config}

Use `TYPO3_CONTEXT` to apply different settings per
environment:

**config/system/additional.php**

```php
// Production and Staging: enforce passkey-only login
if (str_starts_with(
    (string)getenv('TYPO3_CONTEXT'),
    'Production'
)) {
    $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']
        ['nr_passkeys_be']['disablePasswordLogin']
        = '1';
}

// Development: keep password login available
// (disablePasswordLogin defaults to '0')
```

Because enforcement is **per user** (only users with
registered passkeys are affected), enabling
[disablePasswordLogin](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-disablepasswordlogin@1.0) on production is safe even
if not all users have passkeys yet -- they can still log in
with a password. Keeping the setting disabled on development
means all users (including those with passkeys) can use
password login for convenience.

> [!TIP]
> Consider enabling on staging first to verify the
> workflow, and communicate the change to backend users
> who already have passkeys -- they will no longer be
> able to fall back to password login.

### Database synchronisation {#deployment-db-sync}

When syncing the production database to staging or local (a
common workflow), the passkey credential table will contain
credentials bound to the production domain. These
credentials will not work on a different domain.

**Exclude the credential table from database syncs:**

**Example: exclude from mysqldump**

```shell
mysqldump \
    --ignore-table=mydb.tx_nrpasskeysbe_credential \
    mydb > dump.sql
```

**Example: exclude in DDEV**

```yaml
ignore_tables:
  - tx_nrpasskeysbe_credential
```

After importing a production database dump, users simply
register fresh passkeys on the local or staging environment.
Because enforcement is per user, users with no credentials
in the table can log in with a password regardless of the
[disablePasswordLogin](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-disablepasswordlogin@1.0) setting.

> [!IMPORTANT]
> **Exclude the credential table from syncs** when
> [disablePasswordLogin](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-disablepasswordlogin@1.0) is enabled. The per-user
> enforcement counts all non-deleted, non-revoked
> credentials regardless of which domain they were
> registered on (see [ADR-0002](https://github.com/netresearch/t3x-nr-passkeys-be/blob/main/docs/adr/0002-no-rpid-aware-enforcement.md)).
> If production credentials are imported into a different
> environment, users appear to have passkeys (blocking
> password login) even though those passkeys do not work
> on the new domain.

> [!NOTE]
> You do **not** need to exclude `be_users` or any
> other table. Only `tx_nrpasskeysbe_credential` is
> domain-specific. No security data is exposed by an
> accidental sync because the public keys are useless
> without the private keys stored on users'
> authenticators.

## Shared rpId across subdomains {#shared-rpid-across-subdomains}

WebAuthn allows the [rpId](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-rpid@1.0) to be set to a
registrable domain suffix. For example, setting `rpId`
to `example.com` allows passkeys registered on
`staging.example.com` to also work on
`www.example.com`.

> [!WARNING]
> Sharing passkeys across environments is **not
> recommended**. It requires synchronising:
>
> -   The **TYPO3** `encryptionKey`
>     (`$GLOBALS['TYPO3_CONF_VARS']['SYS']`
>     `['encryptionKey']`) -- the extension derives user
>     handles from it cryptographically. Different keys
>     produce different handles, making credentials
>     unresolvable.
> -   The **credential table**
>     (`tx_nrpasskeysbe_credential`) -- the public key
>     material and metadata must be present on both
>     systems.
> -   The **backend user UIDs** (`be_users.uid`) -- user
>     handles are derived from the UID.
>
> Sharing the `encryptionKey` between environments
> creates a **cross-environment attack vector**: if a
> staging environment is compromised, the attacker can
> forge CSRF tokens, session tokens, and passkey challenge
> tokens that are valid on production. Staging
> environments typically have weaker access controls and
> debug mode enabled, making them a more attractive
> target. The `encryptionKey` must be unique per
> environment and treated as a production secret.

If you still need shared subdomains (e.g. `staging` and
`www`), set [rpId](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-rpid@1.0) only on those environments
and keep local development on auto-detect:

**config/system/additional.php**

```php
if (str_starts_with(
    (string)getenv('TYPO3_CONTEXT'),
    'Production'
)) {
    $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']
        ['nr_passkeys_be']['rpId'] = 'example.com';
    // Leave 'origin' empty -- auto-detected per
    // subdomain. Setting it explicitly would break
    // verification on other subdomains.
}
// Development: rpId stays empty
// -> auto-detect -> mysite.ddev.site
```

> [!IMPORTANT]
> Changing the [rpId](https://docs.typo3.org/permalink/netresearch/nr-passkeys-be:confval-rpid@1.0) invalidates all existing
> passkey registrations. Users must register new passkeys
> after the change.
