Passkey creation fails with SecurityError when the origin is app.tempo.local and the RP ID is tempo.local.
Chrome validates WebAuthn RP IDs against the Public Suffix List (PSL). Both the origin host and the claimed RP ID must have a registry-controlled domain per the PSL. .local is not in the PSL — it's an IANA special-use domain reserved for mDNS (RFC 6762).
The rejection happens in OriginIsAllowedToClaimRelyingPartyId:
if (!net::registry_controlled_domains::HostHasRegistryControlledDomain(
caller_origin.host(),
net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES) ||
!net::registry_controlled_domains::HostHasRegistryControlledDomain(
claimed_relying_party_id,
net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
// This prevents "https://login.awesomecompany" from claiming
// "awesomecompany", which is allowed by the spec but disallowed by
// chromium.
return false;
}HostHasRegistryControlledDomain("tempo.local", ...) returns false because .local has no PSL entry. The function doesn't explicitly name .local — the rejection is implicit via the PSL lookup returning no registry.
The unit tests confirm the pattern for non-PSL domains:
// Internal labels (disallowed by Chromium)
{"https://login.awesomecompany", "awesomecompany", false},tempo.local falls into the same category — a domain under a TLD with no PSL entry.
| Origin | RP ID | Result |
|---|---|---|
https://localhost:3001 |
localhost |
✅ Passkey created (localhost has a special-case bypass in Chrome) |
https://app.tempo.local:3001 |
tempo.local |
❌ SecurityError — .local not in PSL |
https://wallet.tempo.xyz |
tempo.xyz |
✅ .xyz is in the PSL |
- Chromium RP ID validation:
webauthn_security_utils.cc—OriginIsAllowedToClaimRelyingPartyId() - Chromium test cases:
webauthn_security_utils_unittest.cc— internal label rejection pattern - IANA special-use domains: https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml —
local.is reserved - Public Suffix List: https://publicsuffix.org/list/public_suffix_list.dat —
.localabsent - RFC 6762 (mDNS): https://www.rfc-editor.org/rfc/rfc6762 — defines
.localas reserved for multicast DNS