LayerZeroFault
passkey recovery Updated:

Turnkey WebAuthn rp.id Origin Mismatch NotAllowedError Fix

VV

Written by

Updated:

Technical Guide

The Turnkey WebAuthn ‘NotAllowedError: The relying party ID is not a valid domain string’ occurs when the rp.id specified in publicKeyCredentialCreationOptions does not match the effective domain or a registrable domain suffix of window.location.origin. In cross-origin iframe architectures, browsers strictly enforce origin boundaries. Configuring the Turnkey embedded wallet origin or proxying authentication through the parent domain fixes the mismatch.

Before executing state changes or signing transactions, verify your cryptographic parameters using our interactive WebAuthn Passkey Debugger utility.


Architectural Breakdown: WebAuthn Relying Party Constraints

The WebAuthn standard enforces strict origin binding to prevent credential phishing across disparate websites.

The W3C rp.id Rules

Under section 5.4.2 of the WebAuthn Level 2 / Level 3 specification:

  1. Exact Match: The rp.id may match the current document’s hostname (e.g. Origin: https://wallet.example.com $\rightarrow$ rp.id: "wallet.example.com").
  2. Registrable Domain Suffix: The rp.id may be any registrable domain suffix of the current host (e.g. Origin: https://wallet.example.com $\rightarrow$ rp.id: "example.com").
  3. Forbidden Schemes & Ports: The rp.id MUST NOT include protocols (https://), port numbers (:3000), or paths (/login).
  4. Public Suffix List (PSL) Ban: The rp.id MUST NOT be a bare effective top-level domain (e.g. com, org, co.uk, or multi-tenant domains like vercel.app or pages.dev).
  5. IP Addresses: WebAuthn forbids domain suffixes on raw IP addresses. On http://192.168.1.50:3000, rp.id must be omitted or match the raw IP string exactly.

Step-by-Step Resolution Protocol

To eliminate rp.id mismatches across localhost, preview branches, and production deployments, implement this three-part dynamic configuration framework.

1. Implement Dynamic Environment-Aware rp.id Resolver

Instead of hardcoding a static domain string into your Turnkey or WebAuthn configuration, compute the valid rp.id dynamically at runtime:

// src/utils/getWebAuthnRpId.ts
export function getValidRelyingPartyId(): string {
  if (typeof window === 'undefined') return 'localhost';

  const hostname = window.location.hostname;

  // 1. Local Development
  if (hostname === 'localhost' || hostname === '127.0.0.1') {
    return 'localhost';
  }

  // 2. Multi-tenant preview deployments (e.g., your-branch.vercel.app)
  // On vercel.app, you CANNOT use 'vercel.app' as rp.id because it is in the PSL.
  // You must use the full subdomain hostname.
  if (hostname.endsWith('.vercel.app') || hostname.endsWith('.pages.dev')) {
    return hostname;
  }

  // 3. Custom Production Domain (e.g., app.layerzero.com -> layerzero.com)
  const parts = hostname.split('.');
  if (parts.length >= 2) {
    // Extract root apex domain (last two segments, e.g. "dapp.xyz")
    return parts.slice(-2).join('.');
  }

  return hostname;
}

2. Configure Turnkey Passkey Client with Dynamic rp.id

Integrate the dynamic resolver into your Turnkey SDK initialization:

// src/auth/turnkeyClient.ts
import { TurnkeyClient } from '@turnkey/http';
import { createPasskeyService } from '@turnkey/sdk-browser';
import { getValidRelyingPartyId } from '../utils/getWebAuthnRpId';

export function initializeTurnkeyPasskeyService() {
  const currentRpId = getValidRelyingPartyId();
  console.log(`[TurnkeyGuard] Initializing WebAuthn with rp.id: ${currentRpId}`);

  return createPasskeyService({
    rp: {
      id: currentRpId,
      name: 'LayerZeroFault Secure Wallet',
    },
    // Enforce required user verification for smart contract compatibility
    userVerification: 'required',
  });
}

3. Cross-Subdomain Passkey Configuration (.well-known/webauthn)

If your architecture utilizes multiple distinct origins (e.g. auth.dapp.xyz, app.dapp.xyz, and pay.dapp.xyz), publish a WebAuthn Related Origins manifest at the apex domain to permit cross-origin passkey assertions:

// public/.well-known/webauthn
{
  "origins": [
    "https://auth.dapp.xyz",
    "https://app.dapp.xyz",
    "https://pay.dapp.xyz"
  ]
}

Note: The .well-known/webauthn file must be served with the Content-Type: application/json header and accessible without redirects over HTTPS.


Environment Configuration Reference Table

Target EnvironmentCurrent HostnameValid rp.id OptionsInvalid rp.id Values (Will Error)
Localhostlocalhost:3000"localhost""http://localhost", "127.0.0.1:3000"
Vercel Previewpr-42.dapp.vercel.app"pr-42.dapp.vercel.app""vercel.app" (Blocked by PSL)
Subdomainapp.dapp.xyz"app.dapp.xyz", "dapp.xyz""xyz", "https://dapp.xyz", "other.com"
Apex Productiondapp.xyz"dapp.xyz""www.dapp.xyz"

[!TIP] Companion Diagnostic Guide: If experiencing related infrastructure failures, cross-reference symptoms with our technical breakdown on Safari ITP Blocking Passkey Smart Wallets Storage Access Rejection.

Frequently Asked Questions

Q: Why can’t I use vercel.app as my rp.id across all preview deployments?

The Public Suffix List (PSL) designates vercel.app as a public suffix to prevent unrelated users from reading cookies or claiming passkeys registered by other Vercel users. The browser enforces this by rejecting any rp.id matching a PSL entry.

Q: What happens to user passkeys if I change my domain name?

Passkeys are cryptographically anchored to the rp.id under which they were generated. If you change your domain from oldbrand.com to newbrand.com, existing passkeys will not be found by the browser on the new domain. You must implement a seed or email-based recovery mechanism to allow users to register a new passkey under the new domain.

Q: Does rp.id case-sensitivity matter?

Yes. The W3C specification mandates lowercase ASCII representations for Relying Party IDs. Never pass capitalized characters (e.g. DApp.xyz) to the WebAuthn API.

Interactive Diagnostic Sandbox

Local WebAuthn & Smart Account Sandboxes

Debug WebAuthn credential requests, PRF extensions, and simulate ERC-4337 passkey account validation in client WebCrypto.

Companion Diagnostic Guide Safari ITP Blocking Passkey Smart Wallets Storage Access Rejection →

Fix Safari Intelligent Tracking Prevention (ITP) blocking passkey authentication and cross-origin iframe storage access in ERC-4337 smart wallets.

Read Guide
Contextual Architecture

Related Diagnostic Guides

View All in Silo →

Related Inquiries

What causes the WebAuthn NotAllowedError rp.id mismatch?

The W3C WebAuthn specification requires that the rp.id parameter must be an exact match or a registrable domain suffix (e.g. 'example.com' for 'app.example.com') of the document origin. If an app attempts to claim an unrelated rp.id or runs on localhost without proper configuration, the browser throws NotAllowedError.

How do I configure Turnkey embedded wallet for multiple environments?

In the Turnkey dashboard, register all active deployment domains (e.g. localhost, staging.dapp.com, dapp.com) under Allowed Origins. In your frontend client initialization, ensure the rpId matches the exact active hostname.

Can I use IP addresses or custom schemes as WebAuthn rp.id?

No. The WebAuthn specification strictly forbids raw IP addresses (including 127.0.0.1) and custom schemes as rp.id. Always use 'localhost' or a fully qualified domain name (FQDN).

How can I inspect clientDataJSON and rp.id parameters locally?

Test your browser's WebAuthn clientDataJSON hashing and relying party ID handling using our client-side WebAuthn Passkey Debugger tool.