Skip to main content
Dfns uses WebAuthn (passkeys) for user authentication. This guide explains how to configure the relying party ID (rpId) for different scenarios.

What is rpId?

The relying party ID (rpId) is a domain identifier that ties passkey credentials to your application. It determines which domain(s) can use a passkey credential.
// When creating a passkey
const credential = await navigator.credentials.create({
  publicKey: {
    rp: {
      id: 'example.com',        // This is the rpId
      name: 'My Application'
    },
    // ... other options
  }
})
A passkey created with one rpId cannot be used with a different rpId. Choose your rpId carefully before deploying to production.

rpId rules

  1. Must be a valid domain - e.g., example.com, not https://example.com
  2. Must match or be a parent of the current origin - A passkey for example.com works on app.example.com, but not vice versa
  3. Cannot be a public suffix - e.g., com, co.uk are not allowed
Your domainValid rpId values
app.example.comapp.example.com, example.com
example.comexample.com only
sub.app.example.comsub.app.example.com, app.example.com, example.com

When to specify rpId

Registration

When registering users via Delegated Registration:
Frontend
import { WebAuthn } from '@dfns/sdk-browser'

const webauthn = new WebAuthn({
  rpId: 'example.com', // Your domain
})

const attestation = await webauthn.create(challenge)

Login

When logging in via Delegated Login:
Frontend
const webauthn = new WebAuthn({
  rpId: 'example.com', // Must match registration rpId
})

const assertion = await webauthn.sign(challenge)

User action signing

When signing user actions:
Frontend
const webauthn = new WebAuthn({
  rpId: 'example.com',
})

const signedChallenge = await webauthn.sign(userActionChallenge)

Recovery

When recovering an account:
Frontend
const webauthn = new WebAuthn({
  rpId: 'example.com', // Must match original registration
})

const attestation = await webauthn.create(recoveryChallenge)

Development vs production

Local development

For local development, use localhost:
const webauthn = new WebAuthn({
  rpId: 'localhost',
})
Passkeys created on localhost only work on localhost. You’ll need to create new passkeys when moving to production.

Staging environments

For staging, use a subdomain of your production domain:
// Staging: staging.example.com
const webauthn = new WebAuthn({
  rpId: 'example.com', // Use parent domain
})
This allows passkeys to work on both staging and production.

Production

Use your root domain for maximum flexibility:
// Production: app.example.com
const webauthn = new WebAuthn({
  rpId: 'example.com', // Root domain
})

Dashboard configuration

Whitelist your domains in the Dfns dashboard:
  1. Navigate to Settings > Passkeys
  2. Add your relying party domain (e.g., example.com)
  3. Choose origin settings:
    • All origins (*): Allows all subdomains and associated mobile apps
    • Specific origins: Restrict to specific URLs if needed
Passkey Settings
Using the root domain (e.g., example.com) as rpId allows passkeys to work across all subdomains (app.example.com, staging.example.com) and associated mobile apps.

Mobile apps

For mobile apps (iOS, Android, React Native, Flutter), the rpId must match a domain you control, and you must host association files.
Mobile apps cannot use localhost for passkeys. You must use a real domain with proper association files, even during development.

1. Whitelist your domain in Dfns

Before configuring your app, add your domain to Dfns:
  1. Go to Settings > Passkeys in the dashboard
  2. Add your domain (e.g., example.com)

2. Host domain association files

iOS and Android fetch these files to verify your app is associated with the domain. Passkeys won’t work if these files are unavailable.
Host at https://example.com/.well-known/apple-app-site-association:
{
  "webcredentials": {
    "apps": ["TEAM_ID.com.example.app"]
  }
}
Replace:
  • TEAM_ID with your Apple Developer Team ID
  • com.example.app with your app’s bundle identifier
The file must be served with Content-Type: application/json and no redirects.

3. Configure your app

Add Associated Domains entitlement:In Xcode, go to your target’s Signing & Capabilities and add:
webcredentials:example.com
Or in your .entitlements file:
<key>com.apple.developer.associated-domains</key>
<array>
  <string>webcredentials:example.com</string>
</array>
Configure the signer:
let signer = PasskeysSigner(relyingPartyId: "example.com")

Testing mobile passkeys

PlatformSimulator/EmulatorReal device
iOSRequires macOS Sonoma+, Xcode 15+Full support
AndroidRequires Google account signed inFull support
For development, you can use a staging domain (e.g., staging.example.com) with the same root rpId (example.com). Passkeys created on staging will work in production if both use the same rpId.

Troubleshooting

”The relying party ID is not a registrable domain suffix”

Your rpId is invalid. Ensure it’s:
  • A valid domain (not a URL)
  • Not a public suffix
  • A parent or exact match of your current origin

”Credential not found” during login

The passkey was created with a different rpId. Users need to re-register with the correct rpId.

Cross-origin issues

If your frontend and backend are on different domains:
  • rpId should match your frontend domain
  • Backend API calls don’t use rpId (they use the auth token)

Complete example

Frontend - webauthn-config.ts
import { WebAuthn } from '@dfns/sdk-browser'

// Configure based on environment
const getRpId = () => {
  if (typeof window === 'undefined') return undefined

  const hostname = window.location.hostname

  if (hostname === 'localhost') {
    return 'localhost'
  }

  // Use root domain for all environments
  // e.g., app.example.com -> example.com
  const parts = hostname.split('.')
  if (parts.length >= 2) {
    return parts.slice(-2).join('.')
  }

  return hostname
}

export const webauthn = new WebAuthn({
  rpId: getRpId(),
})