Skip to content

feat: WebAuthn/passkey support on Linux via electron-webauthn-linux#2337

Open
phelix001 wants to merge 9 commits intoferdium:developfrom
phelix001:fix/webview-permission-handlers
Open

feat: WebAuthn/passkey support on Linux via electron-webauthn-linux#2337
phelix001 wants to merge 9 commits intoferdium:developfrom
phelix001:fix/webview-permission-handlers

Conversation

@phelix001
Copy link

@phelix001 phelix001 commented Feb 11, 2026

Summary

  • Integrates electron-webauthn-linux to provide native passkey/WebAuthn support on Linux, where Electron's built-in WebAuthn UI is non-functional
  • Injects WebAuthn monkey-patches via Chrome DevTools Protocol (Page.addScriptToEvaluateOnNewDocument) into both service webviews and popup BrowserWindows
  • Uses CDP Runtime.addBinding as an IPC bridge for popup windows that lack preload scripts (e.g. Google passkey settings page)
  • Patches navigator.userAgentData.brands to include "Google Chrome" so Google allows passkey creation
  • Adds explicit permission request handlers for webview sessions (media, notifications, clipboard, fullscreen, HID/USB/Serial)

Context

Electron on Linux has no WebAuthn support — navigator.credentials.create/get silently fails because Electron's WebAuthenticationDelegate is a stub. This blocks passkey login flows for services like Google, Microsoft, GitHub, etc.

The electron-webauthn-linux package (separate repo) provides:

  • Software passkey authenticator via 1Password's open-source passkey-rs Rust crates (compiled to native Node addon via napi-rs)
  • Authenticator selection dialog
  • Full WebAuthn L3 spec compliance

Architecture

This PR wires it into Ferdium with three integration points:

  1. Main process (src/index.ts): calls setupWebAuthn() after app.on('ready'), then uses CDP to inject the page script into every webContents (webviews AND popup windows)
  2. Webview preload (src/webview/recipe.ts): exposes window.electronWebAuthn IPC bridge via contextBridge for the standard path
  3. Popup windows: Uses CDP Runtime.addBinding('__webauthnBridge') since popups don't have the recipe preload. The page script detects this and falls back to the CDP bridge.

The page script monkey-patches:

  • navigator.credentials.create/get to route through IPC to the native Rust authenticator
  • PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable to return true
  • navigator.userAgentData.brands to include "Google Chrome" (required by Google for passkey creation)

All WebAuthn code is gated behind isLinux — no impact on macOS/Windows.

The dependency electron-webauthn-linux@0.1.0 is published on npm.

Test plan

  • pnpm typecheck passes
  • pnpm lint passes (zero warnings)
  • pnpm test passes
  • Full pnpm prepare-code passes via pre-commit hook
  • Manual: Google passkey creation via Security > Passkeys (popup window flow)
  • Manual: Google passkey "Try it out" authentication
  • Manual: Passkey auto-sign-in on service re-add
  • Manual: verify non-Linux platforms are unaffected
  • Manual: verify other services (Slack, WhatsApp, etc.) load normally

🤖 Generated with Claude Code

phelix001 and others added 2 commits February 11, 2026 04:57
Webview sessions previously had no setPermissionRequestHandler, causing
all permissions to be auto-granted by Electron's default behavior. This
adds an explicit allowlist for safe permissions (media, notifications,
clipboard, etc.) and enables HID/USB/Serial permission checks needed for
hardware FIDO2 security key detection. Unknown permissions are denied
and logged for debugging.

Related: ferdium#1487, ferdium#2316

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds WebAuthn/passkey support for service webviews on Linux by wiring
in the electron-webauthn-linux package. Registers IPC handlers in the
main process, exposes the WebAuthn bridge via contextBridge in the
preload, and injects the navigator.credentials monkey-patch page script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phelix001 phelix001 changed the title fix: add explicit permission handlers for service webview sessions feat: WebAuthn/passkey support on Linux via electron-webauthn-linux Feb 11, 2026
phelix001 and others added 2 commits February 16, 2026 17:44
…exist

- Only inject webauthnPageScript on dom-ready when hasStoredCredentials()
  returns true for the current hostname, preventing Google from showing
  dead-end passkey prompts when the credential store is empty
- Move WebAuthn page script injection from preload (always) to main
  process dom-ready handler (conditional)
- Add hasCredentials IPC bridge to preload for page-level credential checks
- Guard electronWebAuthn contextBridge behind process.platform === 'linux'
- Add error logging to ErrorBoundary componentDidCatch
- Fix AppStore._readSandboxes to handle non-array JSON data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move CDP-based WebAuthn page script injection from webview-only to all
webContents types, enabling passkey support in popup BrowserWindows
(e.g. Google passkey settings page).

Key changes:
- Add Page.enable for script persistence across navigations
- Add Runtime.addBinding CDP bridge for popup windows without preloads
- Route CDP bridge calls to AuthenticatorManager for create/get/hasCredentials

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Integrates Linux-native WebAuthn/passkey support into Ferdium by wiring electron-webauthn-linux into the main process and webview/popup contexts, aiming to make navigator.credentials.create/get functional on Linux where Electron’s built-in WebAuthn is stubbed.

Changes:

  • Add electron-webauthn-linux dependency and initialize WebAuthn support on Linux at app startup.
  • Inject a WebAuthn monkey-patch into all webContents via Chrome DevTools Protocol (CDP), including a CDP binding bridge for popups without preloads.
  • Add a Linux-only IPC bridge in the webview preload and harden sandbox JSON parsing.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/index.ts Initializes WebAuthn on Linux, injects WebAuthn patching via CDP, adds popup bridging, and updates permission/window-open handling for webviews.
src/webview/recipe.ts Exposes a Linux-only window.electronWebAuthn IPC bridge from the webview preload.
src/stores/AppStore.ts Makes sandbox config parsing resilient to non-array JSON content.
src/components/util/ErrorBoundary/index.tsx Adds error logging details in componentDidCatch.
package.json Adds electron-webauthn-linux dependency.
pnpm-lock.yaml Locks the new dependency and its resolution metadata.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

src/index.ts:450

  • The injected dom-ready diagnostics (executing arbitrary JS and logging DIAG ...) will ship in production and run on matching URLs. This adds noise, can leak page state into logs, and increases risk by injecting extra JS into third-party pages. Please remove this block or gate it behind isDevMode / an explicit debug flag.
        // Diagnostic: check WebAuthn API state after page loads
        contents.on('dom-ready', () => {
          const url = contents.getURL();
          if (url.includes('google.com') || url.includes('myaccount')) {
            contents

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

phelix001 and others added 2 commits February 24, 2026 16:14
- Remove production diagnostic block that injected JS into Google pages
  and logged page state on every dom-ready. Debug-only code, should
  never have shipped.

- Replace naive rootDomain (.split('.').slice(-2)) with tldts.getDomain()
  for correct eTLD+1 matching in the popup setWindowOpenHandler. The old
  heuristic treated evil.co.uk and google.co.uk as the same domain.

- Fix misleading comment on permission handlers to accurately describe
  that setPermissionRequestHandler gates grants while
  setPermissionCheckHandler allows hid/serial/usb feature detection
  (actual device access goes through select-*-device session events).

Re: Copilot's other two comments --

"Permission handler too broad": Ferdium is a messaging service
aggregator. Services need media (voice/video), notifications (message
alerts), display-capture (screen sharing), clipboard, and fullscreen to
function. This is the minimum viable set, not an over-grant. Prompting
per-service/per-origin would break core UX for every user to defend
against a threat model that doesn't apply -- these are user-chosen
services running in isolated webview partitions, not untrusted iframes.

"Add hid/usb/serial to setPermissionRequestHandler": That's not how
Electron works. HID, USB, and Serial device access is gated by
select-hid-device / select-usb-device / select-serial-port session
events, not setPermissionRequestHandler. Adding them there does nothing.
The setPermissionCheckHandler correctly includes them so that
navigator.hid/usb/serial feature detection returns true. Copilot
confused the check handler (feature detection) with the request handler
(permission grants).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phelix001
Copy link
Author

@SpecialAro Almost -- the code is functionally complete and all checks pass. One remaining pre-merge item: the electron-webauthn-linux dependency is currently a local file: link for development. It needs to be published to npm and the specifier updated before this is merge-ready. Working on that.

Addressed all of Copilot's review comments in b1db549:

  • Removed production diagnostic block (leftover debug code)
  • Replaced naive domain matching with tldts for correct eTLD+1 comparison
  • Fixed misleading comment on permission handlers

The other two Copilot comments (permission set "too broad" and adding hid/usb/serial to the request handler) were incorrect -- explained in the thread replies.

phelix001 and others added 2 commits February 24, 2026 16:33
…x@0.1.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CDP debugger.attach() on every webContents triggered Google's automation
detection, causing "Couldn't sign you in - This browser or app may not
be secure" on all Google services.

Replace the entire CDP approach with preload-based injection:
- recipe.ts: inject page script at document-start via DOM <script>
  element, which runs in the main world before page scripts
- New webauthn-popup-preload.ts: same pattern for popup windows
  that don't get the recipe preload
- setWindowOpenHandler passes popup preload via
  overrideBrowserWindowOptions on Linux

This removes ~190 lines of CDP code (debugger.attach,
Page.addScriptToEvaluateOnNewDocument, Runtime.addBinding, the
entire CDP bridge handler) and eliminates all automation signals
that Google detects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@IsmaelMartinez
Copy link

@phelix001 , can you make the electron-webauthn-linux public? Thanks

@phelix001
Copy link
Author

phelix001 commented Mar 11, 2026

https://github.com/phelix001/electron-webauthn-linux done. !@IsmaelMartinez

Picks up fixes for missing hasCredentials in contextBridge and
webview preload bridge setup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phelix001
Copy link
Author

Bumped to electron-webauthn-linux@0.1.1 — fixes contextBridge bridge setup and hasCredentials IPC binding. Package is live on npm: https://www.npmjs.com/package/electron-webauthn-linux

@IsmaelMartinez
Copy link

I will have a look. Probably add a README.md and LICENSE files, etc. Just tell claude to tidy it up a bit ;)

@IsmaelMartinez
Copy link

IsmaelMartinez commented Mar 12, 2026

Hi @phelix001 , at the moment, I don't think this would work for our project (teams-for-linux). But I might explore using some of the ideas, and the libraries you use.

If that is the case, I will put the right attribution (link you/and repo in the code and release) and ping you if I get it to work. Maybe I end up delegating this to your library but making it compatible with our app will make it incompatible with ferdium and 99% of electron apps.

Looking forward to see this integrated in this ferdium-app. Take care and thanks for contributing to the community.

FYI: Goes without saying that if I find any improvements that can be shared for your library/repo, I will create a PR.

@phelix001
Copy link
Author

Sounds good @IsmaelMartinez, feel free to use whatever is useful. I only worked on this because I want to fix passkeys in Ferdium — that's the goal. Happy to help if you have questions about the approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants