---
title: "Configuration — Passkeys for Laravel | Roundly"
description: "Every config key with its env variable and default — relying party, origins, algorithms, challenges, attestation trust and user wiring."
url: https://roundly-consulting.com/open-source/docs/passkeys-for-laravel/configuration
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Passkeys for Laravel](https://roundly-consulting.com/open-source/docs/passkeys-for-laravel.md)

# Configuration

config/passkeys.php documents every option. It is parsed into a typed PasskeyConfig and validated when the app boots, so a misconfiguration fails there rather than at the first registration. The security-critical values are rp.id and origins — they cannot be safely defaulted and must be set for a ceremony to run. The published file, without its block comments:

```php
use RoundlyConsulting\Crypto\Cose\CoseAlgorithm;
use RoundlyConsulting\Passkeys\Enums\AttestationConveyance;
use RoundlyConsulting\Passkeys\Enums\AttestationTrust;
use RoundlyConsulting\Passkeys\Enums\ResidentKey;
use RoundlyConsulting\Passkeys\Enums\SignCountPolicy;
use RoundlyConsulting\Passkeys\Enums\UserVerification;
use RoundlyConsulting\Passkeys\Models\Passkey;

return [
    'rp' => [
        'id' => env('PASSKEYS_RP_ID'),
        'name' => env('PASSKEYS_RP_NAME', env('APP_NAME', 'Laravel')),
    ],

    'origins' => array_values(array_filter(
        array_map('trim', explode(',', (string) env('PASSKEYS_ORIGINS', ''))),
    )),
    'allow_cross_origin' => env('PASSKEYS_ALLOW_CROSS_ORIGIN', false),

    'algorithms' => [
        CoseAlgorithm::ES256->value,   // -7
        CoseAlgorithm::RS256->value,   // -257
        // CoseAlgorithm::EdDSA->value, // -8 (requires ext-sodium)
    ],

    'timeout_ms' => env('PASSKEYS_TIMEOUT_MS', 60_000),
    'attestation' => env('PASSKEYS_ATTESTATION', AttestationConveyance::None->value),
    'user_verification' => UserVerification::Required->value,
    'resident_key' => env('PASSKEYS_RESIDENT_KEY', ResidentKey::Required->value),

    'challenge' => [
        'store' => env('PASSKEYS_CHALLENGE_STORE'),
        'ttl' => env('PASSKEYS_CHALLENGE_TTL', 60),
        'bytes' => 32,
    ],

    'sign_count_policy' => SignCountPolicy::Flag->value,

    'attestation_trust' => env('PASSKEYS_ATTESTATION_TRUST', AttestationTrust::Ignore->value),
    'reject_unknown_fmt' => env('PASSKEYS_REJECT_UNKNOWN_FMT', false),
    'attestation_anchors' => [
        'defaults' => env('PASSKEYS_ATTESTATION_DEFAULT_ANCHORS', true),
        'paths' => [
            // 'packed' => [storage_path('webauthn/vendor-fido-ca.pem')],
        ],
    ],
    'attestation_clock_skew' => env('PASSKEYS_ATTESTATION_CLOCK_SKEW', 60),
    'aaguids' => [
        'allowed' => array_values(array_filter(
            array_map('trim', explode(',', (string) env('PASSKEYS_AAGUIDS_ALLOWED', ''))),
        )),
    ],

    'user' => [
        'handle_column' => env('PASSKEYS_USER_HANDLE_COLUMN', 'passkey_user_handle'),
        'handle_bytes' => 32,
        'name_attribute' => 'email',
        'display_name_attribute' => 'name',
    ],

    'model' => Passkey::class,
    'table' => 'passkeys',
    'key_type' => env('PASSKEYS_KEY_TYPE', 'bigint'),
];
```

## Every key

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `rp.id` | `PASSKEYS_RP_ID` | host of app.url | Relying-party ID — a registrable-domain suffix of every origin (e.g. example.com). |
| `rp.name` | `PASSKEYS_RP_NAME` | APP\_NAME | Human-readable RP name shown by the authenticator. |
| `origins` | `PASSKEYS_ORIGINS` | \[\] | Comma-separated allow-list of exact clientData.origin values (spaces around each entry are trimmed); at least one is required. |
| `allow_cross_origin` | `PASSKEYS_ALLOW_CROSS_ORIGIN` | false | Accept a cross-origin (iframe) ceremony. |
| `algorithms` | — | ES256, RS256 | COSE algorithms offered and accepted, in preference order. |
| `timeout_ms` | `PASSKEYS_TIMEOUT_MS` | 60000 | Ceremony timeout hint sent to the browser, at least 1. |
| `attestation` | `PASSKEYS_ATTESTATION` | none | Attestation conveyance: none, indirect or direct. |
| `user_verification` | — | required | User-verification requirement: required, preferred or discouraged. |
| `resident_key` | `PASSKEYS_RESIDENT_KEY` | required | Discoverable-credential posture; required keeps usernameless login. |
| `challenge.store` | `PASSKEYS_CHALLENGE_STORE` | default store | Cache store name for challenges. |
| `challenge.ttl` | `PASSKEYS_CHALLENGE_TTL` | 60 | Minimum challenge lifetime in seconds, at least 1; a challenge always lives at least as long as its ceremony’s timeout. |
| `challenge.bytes` | — | 32 | Random challenge length in bytes, at least 16. |
| `sign_count_policy` | — | flag | Counter regression: reject throws, flag fires an event and proceeds. |
| `attestation_trust` | `PASSKEYS_ATTESTATION_TRUST` | ignore | Attestation trust ladder: ignore, self or basic. |
| `reject_unknown_fmt` | `PASSKEYS_REJECT_UNKNOWN_FMT` | false | Under ignore, refuse formats the package can’t verify and statements that don’t verify. |
| `attestation_anchors.defaults` | `PASSKEYS_ATTESTATION_DEFAULT_ANCHORS` | true | Trust the shipped Apple WebAuthn and Google hardware-attestation roots. |
| `attestation_anchors.paths` | — | \[\] | format => \[absolute PEM paths\] — your own trust anchors. |
| `attestation_clock_skew` | `PASSKEYS_ATTESTATION_CLOCK_SKEW` | 60 | Leeway in seconds (0–3600) on both bounds of a certificate’s validity window. |
| `aaguids.allowed` | `PASSKEYS_AAGUIDS_ALLOWED` | \[\] | Comma-separated AAGUID allow-list (spaces around each entry are trimmed); empty allows every authenticator model. |
| `user.handle_column` | `PASSKEYS_USER_HANDLE_COLUMN` | passkey\_user\_handle | Host column holding the opaque user handle. |
| `user.handle_bytes` | — | 32 | Random bytes in a generated user handle, 16–64. |
| `user.name_attribute` | — | email | Model attribute used as the account name. |
| `user.display_name_attribute` | — | name | Model attribute used as the display name. |
| `model` | — | Passkey::class | The credential model; point it at a Passkey subclass to add behaviour. Anything else throws InvalidConfigurationException. |
| `table` | — | passkeys | The credential table — set it before migrating (the migration reads it). |
| `key_type` | `PASSKEYS_KEY_TYPE` | bigint | Owner morph key type: bigint, uuid or ulid (case-insensitive; unset or blank reads as bigint, anything else throws InvalidConfigurationException) — set it before migrating. |

## Environment

The common knobs are env-driven, so most hosts never publish the config at all:

```bash
PASSKEYS_RP_ID=example.com
PASSKEYS_RP_NAME="Example"
PASSKEYS_ORIGINS=https://example.com,https://www.example.com
PASSKEYS_TIMEOUT_MS=60000
PASSKEYS_CHALLENGE_TTL=60
PASSKEYS_ATTESTATION=none
PASSKEYS_ATTESTATION_TRUST=ignore
```

## Boolean switches

The three switches — allow\_cross\_origin, reject\_unknown\_fmt and attestation\_anchors.defaults — accept the usual env spellings: true/false, 1/0, on/off and yes/no. Not set — absent, null or blank ('', what KEY= in .env gives) — reads as the default shown in the table; anything else (a typo such as disabled) throws InvalidConfiguration naming the key instead of quietly reading as the default:

```bash
PASSKEYS_ALLOW_CROSS_ORIGIN=off
PASSKEYS_REJECT_UNKNOWN_FMT=yes
PASSKEYS_ATTESTATION_DEFAULT_ANCHORS=0
```

## Strict values

Every other key is just as strict. Not set (absent, null or blank) reads as the default; a present value of the wrong shape throws InvalidConfiguration naming the key:

- Integers — timeout\_ms ≥ 1, challenge.ttl ≥ 1, challenge.bytes ≥ 16, user.handle\_bytes 16–64, attestation\_clock\_skew 0–3600 — take an int or a canonical integer string, so “five”, “1.5” or “1e3” throws rather than becoming 0 or being clamped.
- The enum keys — attestation, attestation\_trust, user\_verification, resident\_key and sign\_count\_policy — take a case or its exact value; a typo throws, it never reads as the default.
- origins, aaguids.allowed, algorithms and attestation\_anchors.paths must be lists of valid entries; a non-list or a bad entry throws rather than being dropped. An empty algorithms list throws too — leave the key unset or blank for the defaults. Entries of origins, aaguids.allowed and attestation\_anchors.paths are trimmed, so “https://a.test, https://b.test” in env matches both origins.
- The string keys — rp.name, the user.\* column and attributes, and table — must be strings, and a blank one is not set, so its default applies (rp.name → APP\_NAME); rp.id and challenge.store must be strings when set (blank is not set, so rp.id is derived from app.url and challenges use the default store).

## Fail-fast validation

- A ceremony without rp.id or without any origins throws InvalidConfiguration (missingRpId / emptyOrigins).
- The zero-config defaults always boot — only rp.id and origins wait for a ceremony. The checks below run when the app boots.
- A COSE algorithm outside ES256, RS256 and EdDSA throws InvalidConfiguration at boot.
- attestation\_trust stricter than ignore while attestation is none fails at boot — otherwise every registration would be refused for a reason nobody could see.
- attestation\_clock\_skew outside 0–3600 fails at boot; an unreadable or PEM-less trust-anchor path throws at first use.
- challenge.bytes below 16 or user.handle\_bytes outside 16–64 throws InvalidConfiguration at boot instead of being clamped.

## Checking your setup

The package contributes a section to php artisan about. It reports the security posture — trust tier, conveyance, verification requirements, counts, TTLs and SET/MISSING presence — but never the RP ID, the origins, anchor paths or the AAGUIDs themselves:

```bash
php artisan about
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/passkeys-for-laravel.md)
