Skip to content

fix(auth): route Google passkey sign-in to a working fallback (#291) - #453

Merged
sozercan merged 2 commits into
sozercan:mainfrom
NestDream:fix/login-passkey-dead-end
Aug 24, 2026
Merged

fix(auth): route Google passkey sign-in to a working fallback (#291)#453
sozercan merged 2 commits into
sozercan:mainfrom
NestDream:fix/login-passkey-dead-end

Conversation

@NestDream

Copy link
Copy Markdown
Contributor

Description

Fixes the passkey sign-in dead-end reported in #291.

When signing in to YouTube Music, Kaset loads accounts.google.com in a WKWebView. If the account has passkeys, Google offers passkey authentication, but that ceremony can never complete in an un-entitled WKWebView: window.PublicKeyCredential is exposed (and getClientCapabilities() even reports hybridTransport: true), yet the OS rejects every navigator.credentials.get() almost instantly with NotAllowedError and no system UI. Google reads that as a broken cross-device attempt and shows the unrecoverable "Something went wrong — check that Bluetooth is on and the devices are close together" screen. The only escape today is "Try another way" plus a manual password.

There is no supported way to make the ceremony actually succeed here: the restricted com.apple.developer.web-browser.public-key-credential entitlement is granted by Apple only to general-purpose browsers (and needs an embedded provisioning profile, incompatible with the ad-hoc / Apple Development signing fallbacks), and Associated Domains with google.com are impossible.

This PR takes the mitigation shipping in other WKWebView apps in the same spot (e.g. the Nook browser while awaiting the entitlement): hide WebAuthn from the sign-in page so Google offers a flow that can succeed. LoginPasskeySuppression injects a document-start user script (all frames) into the login / session-switch WebView configuration that:

  1. undefines window.PublicKeyCredential, which Google feature-detects before offering passkeys, and
  2. rejects any navigator.credentials.get/create call requesting a publicKey credential with NotAllowedError (a deterministic version of the error the platform would produce anyway, without the misleading cross-device screen).

Google then routes sign-in through password (or another) verification. Passwordless sign-in via passkey still is not possible in the embedded WebView — that is an unchanged platform limitation — but users are no longer stuck on an error loop.

The script is scoped to createSessionSwitchWebViewConfiguration(), used only by the login sheet and the hidden account-switch navigations. Playback WebViews are untouched.

AI Prompt

🤖 AI Prompt Used
Fix issue #291 in sozercan/kaset: Google passkey sign-in dead-ends on the
"Something went wrong / check Bluetooth" screen in the login WKWebView.

Research the root cause (WKWebView exposes WebAuthn but the OS rejects every
ceremony without the restricted browser entitlement, so Google falls into the
broken cross-device/hybrid flow), then implement a fix that makes Google fall
back to a working sign-in method. Follow AGENTS.md/CONTRIBUTING.md: no
third-party deps, prefer minimal WebView changes, @MainActor on observable
types, Swift Testing for new tests, update the localization catalog plus all
.lproj mirrors together, add an ADR for the design decision, and pass
swift build / swift test --skip KasetUITests / swiftlint --strict / swiftformat.
Verify the fix against the real accounts.google.com page, not just by reasoning.

AI Tool: Claude Code

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Related Issues

Fixes #291

Changes Made

  • Add LoginPasskeySuppression (Sources/Kaset/Services/WebKit/), a document-start / all-frames WKUserScript that hides WebAuthn from Google sign-in pages.
  • Attach it in WebKitManager.createSessionSwitchWebViewConfiguration() (login sheet + account-switch only).
  • Update the login sheet caption to describe the actual behavior, localized across all 17 locales.
  • Add LoginPasskeySuppressionTests (functional WKWebView checks: WebAuthn is exposed in a plain WebView, hidden in the suppressed config, and publicKey requests reject with NotAllowedError) and a WebKitManagerTests guard.
  • Add docs/adr/0033-login-passkey-suppression.md and its index row.

Testing

  • Unit tests pass (swift test --skip KasetUITests — 2986 tests, incl. LocalizationCatalogParityTests)
  • Manual testing performed
  • UI tested on macOS 26+

Verified against the real accounts.google.com login URL in a WKWebView using the shipped script: typeof PublicKeyCredential is "undefined" (vs "function" without the script), the sign-in page renders normally with the email field, and a publicKey get() rejects with the shim's NotAllowedError. swift build, the full unit suite, swiftformat --lint, and swiftlint --strict on the changed files are all clean. The packaged app (Scripts/compile_and_run.sh) launches and runs.

Checklist

  • My code follows the project's style guidelines
  • I have run swiftlint --strict && swiftformat .
  • I have added tests that prove my fix works
  • New and existing unit tests pass locally
  • I have updated documentation if needed (ADR-0033)
  • I have checked for any performance implications
  • My changes generate no new warnings

Additional Notes

Heads-up unrelated to this change: the SwiftLint CI job runs an unpinned brew install swiftlint, and the current 0.65.1 enables legacy_swiftui_aspect_ratio by default, which turns ~42 pre-existing aspectRatio(contentMode:) call sites (all in files this PR does not touch, present on main) into --strict errors. That gate may go red for reasons independent of this PR; happy to send a separate change (disable the rule or migrate the call sites) if useful.

Li Guo and others added 2 commits August 24, 2026 03:09
…an#291)

Google sign-in loaded in Kaset's login WebView offers passkey
authentication, but an un-entitled WKWebView can never complete a
WebAuthn ceremony: the OS rejects every request with NotAllowedError,
and Google renders its unrecoverable "Something went wrong / check
Bluetooth" screen instead of asking for a password.

The only supported way to run real passkey ceremonies for arbitrary
sites is the restricted com.apple.developer.web-browser.public-key-credential
entitlement, granted by Apple only to general-purpose browsers and
incompatible with Kaset's signing setup. Associated Domains with
google.com are also impossible.

Instead, hide WebAuthn from the sign-in page so Google offers a flow
that can succeed. LoginPasskeySuppression injects a document-start user
script (all frames) into the login/session-switch WebView configuration
that undefines window.PublicKeyCredential and rejects publicKey
credential requests with NotAllowedError. Google feature-detects the
API and falls back to password (or another) sign-in.

Also updates the login sheet caption (17 locales) and adds ADR-0033.

Verified: the shim hides PublicKeyCredential on the real
accounts.google.com page while it renders normally; swift build and the
full unit suite pass.
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>

@sozercan sozercan left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

thanks! lgtm

@sozercan
sozercan merged commit edfec10 into sozercan:main Aug 24, 2026
8 checks passed
Yoddikko pushed a commit to Yoddikko/kasetPlus that referenced this pull request Aug 27, 2026
…an#291) (sozercan#453)

Co-authored-by: Li Guo <liguoamz@amazon.com>
Co-authored-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Yoddikko added a commit to Yoddikko/kasetPlus that referenced this pull request Aug 27, 2026
Cherry-picked LoginPasskeySuppression (routes Google passkey sign-in to a
working password/other-method fallback) + WebKitManager wiring + tests + ADR.
Kept fork localizations (--ours); the new passkey strings fall back to English
until translated before a release. Fixed the test import (Kaset->KasetPlus).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012LaRrBbQsc5W5agXFkYc7u
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.

[Bug]: OAuth login fails - requires manual password entry instead of browser authentication

2 participants