Fix: WebAuthn in Cross-Origin Iframes & Permissions-Policy Blocks
If you are building an embedded Web3 application using solutions like Privy, Dynamic, or Web3Auth, you likely rely on an embedded iframe to manage user keys. When implementing passkeys (WebAuthn) inside these cross-origin iframes, you might encounter a critical roadblock: the browser throws a NotAllowedError or a console message declaring:
DOMException: The document is not allowed to use this feature.
This happens because modern browsers lock down security access to physical hardware enclaves (like TPMs, TouchID, and FaceID) inside nested contexts. To make WebAuthn operations function properly inside an iframe, you must explicitly declare a Permissions Policy.
If you are also dealing with Chrome 120+ storage partitioning errors in your embedded flows, read our guide on Fixing Dynamic XYZ WebAuthn Iframe Storage Partitioning to resolve cookie and local storage isolation blocks.
Why Browsers Block WebAuthn in Iframes
WebAuthn allows websites to request cryptographic assertions directly from the device’s hardware enclave. Because this action deals with high-stakes user credentials, browsers enforce a strict security perimeter:
- Phishing Protection: A malicious site could embed a legitimate Web3 login iframe and capture biometric gestures or spoof the user interface.
- Explicit Delegation: The top-level document (your main dApp) must explicitly grant permission to the nested document (the authentication provider’s iframe) to request public key credentials.
If the parent document does not explicitly delegate this capability, any call to navigator.credentials.create() or navigator.credentials.get() within the iframe will immediately throw a security exception.
Step-by-Step Fixes to Restore Passkey Functionality
1. Configure the Iframe HTML Attributes
The most direct fix is to update the <iframe> tag inside your HTML markup. You must add the allow attribute and explicitly pass the required WebAuthn tokens:
<!-- Granting WebAuthn permissions to the embedded iframe -->
<iframe
src="https://auth.embedded-wallet.xyz"
allow="publickey-credentials-get; publickey-credentials-create"
id="auth-frame"
></iframe>
publickey-credentials-get: Required for signing assertions (logging in with an existing passkey).publickey-credentials-create: Required for generating new credentials (registering a new passkey).
If you are using an SDK (such as Privy or Dynamic), ensure you have updated the SDK to its latest version, as older versions might generate iframe elements without these attributes.
2. Set HTTP Permissions-Policy Headers
In addition to the iframe HTML attributes, the parent document’s web server should serve the pages with a matching Permissions-Policy HTTP header. This is a secure fallback that ensures browser extensions or third-party scripts cannot inject unprivileged iframes.
Configure your server (or your Next.js / Astro configuration) to send the following header:
Permissions-Policy: publickey-credentials-get=(self "https://auth.embedded-wallet.xyz"), publickey-credentials-create=(self "https://auth.embedded-wallet.xyz")
This specifies that only the parent domain (self) and the trusted authentication provider (https://auth.embedded-wallet.xyz) are permitted to access the WebAuthn API.
3. Enforce Transient User Activation
Even with correct Permissions-Policy settings, the browser will still block WebAuthn calls inside cross-origin iframes unless they are triggered by Transient User Activation (a direct user gesture).
- Invalid Call Pattern: Calling the WebAuthn API inside a
setTimeout, a resolved promise handler, or during an asynchronousfetchcallback. The browser will determine that the user’s click event has expired and will block the execution. - Valid Call Pattern: The WebAuthn API must be called synchronously inside the click handler:
// Inside the iframe document
loginButton.addEventListener('click', async () => {
// Sync context within user click event ensures transient activation
const credential = await navigator.credentials.get({
publicKey: challengeOptions
});
// Handle credential...
});
Troubleshooting Browser Quirks
- Safari Compatibility: Safari has historically enforced stricter rules than Chrome regarding credential creation inside iframes. If registration fails on iOS/macOS Safari while working on Chrome, ensure that the iframe is completely visible and not hidden using
display: noneorvisibility: hiddenat the moment the WebAuthn prompt is triggered. - Sandbox Attribute Restrictions: If your
<iframe>has asandboxattribute, you must ensure that it includes bothallow-scriptsandallow-same-origin. Withoutallow-same-origin, the iframe is treated as a unique origin and will fail verification checks against the credential server.