Skip to content

Import YouTube session from the user's browser (fixes #8)#9

Merged
gek0z merged 1 commit intomainfrom
fix/import-session-from-browser
Apr 14, 2026
Merged

Import YouTube session from the user's browser (fixes #8)#9
gek0z merged 1 commit intomainfrom
fix/import-session-from-browser

Conversation

@gek0z
Copy link
Copy Markdown
Owner

@gek0z gek0z commented Apr 14, 2026

Summary

Replaces the embedded WKWebView sign-in with an import-from-browser flow modelled on yt-dlp --cookies-from-browser. The user signs in to YouTube once in whatever browser they already use — where passkeys and every password manager work natively — and YouMenuTube imports the resulting youtube.com cookies into the same Keychain slot InnerTube was already reading from. No Associated Domains, no OAuth, no Google Cloud project; the InnerTube code path downstream of the cookie filter is unchanged.

Supports: Safari, Chrome, Edge, Arc, Brave, Vivaldi, Opera, Helium, Firefox. Three on-disk formats cover all nine:

  • SafariCookies.binarycookies inside the Safari container (requires one-time Full Disk Access TCC grant; the Import window links to the right System Settings pane).
  • Chromium family — SQLite + AES-128-CBC with a PBKDF2-derived key from the login Keychain (Helium uses Helium Storage Key rather than the conventional … Safe Storage). One-time Keychain prompt per browser.
  • Firefox — plain SQLite, no prompt.

Notable details

  • Chromium decrypt strips SHA256(host_key)[:16] (the documented binding prefix) plus one additional 16-byte integrity block observed on Chrome 146 and Helium. Empirically constant per machine and across Chromium forks; not in the public Chromium source, but necessary for values to come out clean.
  • ingest() dedupes by name, choosing the cookie whose path best matches /youtubei/v1/…. Without this, Helium's per-path variants (3× VISITOR_INFO1_LIVE, etc.) end up as duplicate name=value pairs in the header and InnerTube answers loggedOut=true.
  • No OAuth reconsidered — verified against Google's docs: Watch Later returns watchLaterNotAccessible 403, Home feed deprecated 2016-09-12, Subs feed has no dedicated endpoint, search quota is 100 units/request. Data API v3 can't replace the three marquee surfaces of this app. InnerTube stays the only path.
  • Sandbox comment updated in project.yml — we stay non-sandboxed so the importer can reach other apps' cookie stores, not (as before) for WKWebView autofill.

Test plan

  • Build + test suite green (xcodebuild … build && … test)
  • swift-format lint --strict clean (pre-commit hook enforces)
  • Chrome import end-to-end: 39 cookies → 26 after dedup → session accepted, Home / Subs / Playlists / Watch Later all working
  • Helium import end-to-end: 29 → 23 after dedup → session accepted (after the browser had enough normal use to be fingerprinted as a real user by Google, which is a universal quirk of fresh Chromium-based sessions)
  • Firefox import
  • Safari import (with Full Disk Access granted)
  • Passkey-only Google account signing in via Safari then importing

The in-app WKWebView sign-in can never support passkeys or
password-manager autofill: passkeys need an Associated Domains entitlement
for google.com that we can't have, and no password manager hooks into an
embedded WKWebView in a third-party app. ASWebAuthenticationSession would
fix autofill but puts the session in the system cookie jar that we can't
read, breaking the InnerTube cookie-capture the entire app is built on.

Switch sign-in to yt-dlp's --cookies-from-browser pattern: read youtube.com
cookies directly from whichever browser the user already uses (Safari,
Chrome, Edge, Arc, Brave, Vivaldi, Opera, Helium, Firefox). The user signs
in in their own browser — where all auth methods work natively — and
YouMenuTube imports the session. Nothing downstream of the cookie filter
changes; same Keychain slot, same YouTubeModel wiring, same InnerTube
calls.

Chromium cookie decrypt handles the modern value layout observed on Chrome
146 and Helium: SHA256(host_key)[:16] binding prefix plus one additional
16-byte integrity block before the real value (undocumented in public
Chromium source but consistent across Chromium-family browsers on a given
machine). Helium's Keychain entry is "Helium Storage Key" rather than the
conventional "… Safe Storage".

ingest() dedupes (name, domain, path) down to one cookie per name matching
the /youtubei/v1/ request path, because browsers store per-path variants
that would otherwise turn into repeated name=value pairs in the Cookie
header and make InnerTube answer loggedOut=true.
@gek0z gek0z changed the title Import YouTube session from the user's browser (fixes #8) Import YouTube session from the user's browser Apr 14, 2026
@gek0z gek0z changed the title Import YouTube session from the user's browser Import YouTube session from the user's browser (fixes #8) Apr 14, 2026
@gek0z gek0z merged commit 441227b into main Apr 14, 2026
1 check passed
@gek0z gek0z deleted the fix/import-session-from-browser branch April 14, 2026 11:36
gek0z added a commit that referenced this pull request Apr 14, 2026
Three UX fixes for the import flow shipped in #9:

- Add a per-browser heads-up banner in the Import window. Chromium
  browsers get a note about the one-time Keychain password prompt and
  that clicking "Always Allow" removes it from future runs. Safari gets
  the Full Disk Access note up-front instead of only after a failed
  attempt. Firefox stays quiet — no OS prompt there.

- Attach an LAContext to the Chromium Safe-Storage keychain query
  (`kSecUseAuthenticationContext`) and a `kSecUseOperationPrompt`
  message. In practice Chromium-family browsers don't create their
  Safe-Storage entry with biometric-compatible ACL flags, so macOS
  still falls back to the password prompt today — but attaching the
  context is free, gives a nicer in-prompt message, and upgrades to
  Touch ID automatically the day a fork (or Chromium itself) flips
  the ACL.

- Fix "Sign in button did nothing": `NSApp.activate(ignoringOtherApps:)`
  was softened in macOS 14 and doesn't reliably pull a fresh window
  forward from an LSUIElement app. Switch to `NSApp.activate()`, and
  poll `NSApp.windows` for the new window for up to 500 ms rather than
  assuming a single-runloop defer is enough — the window isn't always
  in the list when onAppear fires on first open.
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.

1 participant