Conversation
…D_CREDENTIALS) Since ~2026-08-10 Spotify rejects login5 auth derived from an OAuth access token whose client id is not the desktop client (INVALID_CREDENTIALS; BAD_REQUEST when the account client id is passed), as prep for their "Soloist" launch. This kills the `loginWithAccessToken` / `startConnectDeviceWithToken` path, so every zone's Spotify Connect host fails to start and playback can't authenticate — while the Web API (browsing) still works. Affects all librespot-based clients (librespot-org/librespot#1737, devgianlu/go-librespot#364). Tracked in sonn-audio#333. A real Zeroconf/Connect handshake still authenticates (AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS). node-librespot already exposes the primitive (`startZeroconfLogin`) but core never called it. - spotifyStreamingService: `pairLibrespotCredentialsViaZeroconf()` wraps `startZeroconfLogin` (mirrors generateLibrespotCredentialsFromOAuth). - serviceAuth: `handleSpotifyLibrespotZeroconf` — POST /admin/api/spotify/librespot/zeroconf { accountId, deviceName?, timeoutMs? }. Advertises a temporary Connect device; resolves when the user picks it in the Spotify app; persists the returned blob as the account's librespotCredentials and reinitialises the inputs. - spotifyInputService: don't fall back to credPath (a file path) as the credentials blob — pass '' so the intended path is taken and librespot doesn't choke with "invalid credentials json". Validated end-to-end on hardware: after Zeroconf pairing, INVALID_CREDENTIALS went from ~40/30s to 0 across 11 zones and playback authenticates again. WIP: no admin-UI button yet (endpoint only); a "Pair Spotify" action in the UI is the natural follow-up. Pairing yields an account-level blob usable by all zones.
rudyberends
added a commit
that referenced
this pull request
Aug 15, 2026
…cepts Since 2026-08-10 Spotify refuses any login built from an OAuth access token whose client id is not the desktop one. That is every credential this server can produce on its own: `loginWithAccessToken` mints from a token, `startConnectDeviceWithToken` exchanges one internally, and both come out INVALID_CREDENTIALS. Browsing runs on the Web API and is untouched, so an account looks healthy while no zone can authenticate — which is exactly how #333 reads. The same week broke librespot and go-librespot too. Nothing on that path can be repaired, so the recovery written for #333 could not land: every route out of a refusal — re-mint, then fall back to the access token — goes through the closed door. What still authenticates is a blob handed over by the Spotify app during a real handshake, and node-librespot has exposed `startZeroconfLogin` all along without anything here calling it. So: advertise a plain Connect device, wait for someone to pick it, keep what comes back as the account's credentials. Pairing runs as a job rather than on the request, because it finishes when a person taps something and a browser will not hold a POST that long; the screen polls and can say what to go and tap. One handshake per account at a time. A refusal is now a verdict with an expiry rather than a life sentence. That was survivable while minting could replace a retired blob, but it cannot, so a single refusal — including a wrong one against a blob that works — retired that device for the life of the process. And a new account blob clears every verdict recorded against the old one: `credentialsForDevice` consults the refusal flag before falling back to the account payload, so without that a paired account would keep logging in with nothing at all, and pairing would report success while changing nothing until a restart. Refs #333. Zeroconf primitive and endpoint shape from tobsch's #337. Co-authored-by: Tobias Schlottke <tobias@saas.group>
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.
Problem
Since ~2026-08-10 Spotify rejects
login5authentication built from an OAuth access token whose client id isn't the desktop client (INVALID_CREDENTIALS;BAD_REQUESTif the account client id is passed), as prep for their “Soloist” launch (blog 2026-08-13). This killsloginWithAccessToken/startConnectDeviceWithToken, so every zone's Spotify Connect host fails to start and playback can't authenticate, while the Web API (browsing) still works.Affects all librespot-based clients — librespot-org/librespot#1737, devgianlu/go-librespot#364. Tracked here in #333 (multiple users, all on beta.18 by coincidence of timing). Not a core regression: the credential/connect call args are unchanged vs beta.16 and node-librespot is the same 0.5.2.
A real Zeroconf/Connect handshake still authenticates (
AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS). node-librespot already exports the primitive (startZeroconfLogin) — core just never called it.Change
spotifyStreamingService.ts—pairLibrespotCredentialsViaZeroconf()wrappingstartZeroconfLogin(mirrorsgenerateLibrespotCredentialsFromOAuth), plus the addon-interface entry.serviceAuth.ts—handleSpotifyLibrespotZeroconf:POST /admin/api/spotify/librespot/zeroconf { accountId, deviceName?, timeoutMs? }. Advertises a temporary Connect device, resolves when the user selects it in the Spotify app, persists the returned blob as the account'slibrespotCredentials, and reinitialises the inputs. Mirrors the existing…/librespot/oauthhandler.spotifyHandlers.ts— registers the route.spotifyInputService.ts— small correctness fix:credentialsPath: this.credentialsPayload ?? credPathfell back tocredPath(a file path), whichstartNativeConnectHostthen treats as a JSON blob →invalid credentials json. Changed to?? ''so the intended path runs.Verified end-to-end on hardware
Ran the equivalent of the new endpoint (
startZeroconfLogin→ pick device in Spotify app → persist blob). Result:INVALID_CREDENTIALSwent from ~40/30s → 0 across 11 zones; connect hosts start; playback authenticates again. The returned blob isauth_type: 1(stored credentials) — the path Spotify still accepts.WIP
INVALID_CREDENTIALS, vs. keep it an explicit user action (I lean explicit — it requires the user to tap the device in the app).Have a reliable hardware repro; glad to iterate.