fix(auth): route Google passkey sign-in to a working fallback (#291) - #453
Merged
Merged
Conversation
…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>
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes the passkey sign-in dead-end reported in #291.
When signing in to YouTube Music, Kaset loads
accounts.google.comin aWKWebView. If the account has passkeys, Google offers passkey authentication, but that ceremony can never complete in an un-entitledWKWebView:window.PublicKeyCredentialis exposed (andgetClientCapabilities()even reportshybridTransport: true), yet the OS rejects everynavigator.credentials.get()almost instantly withNotAllowedErrorand 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-credentialentitlement 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 withgoogle.comare 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.
LoginPasskeySuppressioninjects a document-start user script (all frames) into the login / session-switch WebView configuration that:window.PublicKeyCredential, which Google feature-detects before offering passkeys, andnavigator.credentials.get/createcall requesting apublicKeycredential withNotAllowedError(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
AI Tool: Claude Code
Type of Change
Related Issues
Fixes #291
Changes Made
LoginPasskeySuppression(Sources/Kaset/Services/WebKit/), a document-start / all-framesWKUserScriptthat hides WebAuthn from Google sign-in pages.WebKitManager.createSessionSwitchWebViewConfiguration()(login sheet + account-switch only).LoginPasskeySuppressionTests(functional WKWebView checks: WebAuthn is exposed in a plain WebView, hidden in the suppressed config, andpublicKeyrequests reject withNotAllowedError) and aWebKitManagerTestsguard.docs/adr/0033-login-passkey-suppression.mdand its index row.Testing
swift test --skip KasetUITests— 2986 tests, incl.LocalizationCatalogParityTests)Verified against the real
accounts.google.comlogin URL in aWKWebViewusing the shipped script:typeof PublicKeyCredentialis"undefined"(vs"function"without the script), the sign-in page renders normally with the email field, and apublicKeyget()rejects with the shim'sNotAllowedError.swift build, the full unit suite,swiftformat --lint, andswiftlint --stricton the changed files are all clean. The packaged app (Scripts/compile_and_run.sh) launches and runs.Checklist
swiftlint --strict && swiftformat .Additional Notes
Heads-up unrelated to this change: the SwiftLint CI job runs an unpinned
brew install swiftlint, and the current 0.65.1 enableslegacy_swiftui_aspect_ratioby default, which turns ~42 pre-existingaspectRatio(contentMode:)call sites (all in files this PR does not touch, present onmain) into--stricterrors. 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.