Authentication & Tokens
Relevant source files
The following files were used as context for generating this wiki page:
Spotty implements a custom authentication system based on Spotify's desktop client authorization flow. It manages OAuth tokens, secure storage, and session lifecycle to authorize access to Spotify's partner APIs and playback services.
The system uses a Proof Key for Code Exchange (PKCE) flow to obtain and refresh tokens without requiring a client secret. Credentials remain local to the user's Mac, stored securely in the macOS Keychain. Sources: README.md, PRIVACY.md, Sources/Spotty/Spotify/KeymasterAuth.swift:150-155
Keymaster Authorization Flow
The "Keymaster" flow represents the primary method for obtaining user authorization. It targets Spotify's own desktop client ID (65b708073fc0480ea92a077233ca87bd) to access restricted partner API scopes. Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:138-140
OAuth Process
- Local Server Startup: The app starts a
LoopbackCallbackServeron an ephemeral port. Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:167-168, Tests/SpottyBoundaryTests/AuthAndParsingChecks.swift:171-175 - Challenge Generation: It generates a PKCE
code_verifierand astateparameter to prevent cross-site request forgery. Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:170-171 - Browser Redirect: The app opens the user's default browser to Spotify's authorization endpoint with the required scopes. Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:173-186
- Callback Handling: The browser redirects to the local loopback server (
http://127.0.0.1:[port]/login) with an authorization code. Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:233-235, Tests/SpottyBoundaryTests/AuthAndParsingChecks.swift:189-195 - Token Exchange: The app exchanges the code for access and refresh tokens via a POST request to
accounts.spotify.com/api/token. Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:191-201
Authentication Sequence
This diagram illustrates the interaction between the Spotty app, the user's browser, and Spotify's authentication servers.
Token Management
Tokens are encapsulated in the KeymasterTokens structure, which tracks the access token, the rotating refresh token, and expiration metadata. Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:19-35
Data Structures
| Field | Type | Description |
|---|---|---|
accessToken | String | Used to authorize requests to Spotify APIs. |
refreshToken | String | Used to obtain a new access token; rotated by Spotify on every refresh. |
expiresAt | Date | The point in time when the current access token becomes invalid. |
username | String | The Spotify account username authorized by the user. |
requiresReauthentication | Bool | A marker indicating if the user must re-authorize due to rejected credentials. |
Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:20-30
Token Refresh Logic
The system refreshes the access token when its remaining validity is less than or equal to 300 seconds (refreshBuffer). Spotify frequently rotates the refresh token during this exchange; the app must store the new refresh token to prevent session loss. Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:75-84, Sources/Spotty/Spotify/KeymasterAuth.swift:204-213
Secure Storage (Keychain)
Spotty uses the macOS Keychain to persist KeymasterTokens. It utilizes kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly to ensure data is protected but available when the app runs in the background. Sources: Sources/Spotty/Spotify/KeychainManager.swift:65-68
Keychain Operations
- Save: Encodes tokens to JSON and writes them as a generic password item under the service
dev.spotty.app.keymaster. If an item exists, it performs an in-place update. Sources: Sources/Spotty/Spotify/KeychainManager.swift:21-30, Sources/Spotty/Spotify/KeychainManager.swift:78-83 - Load: Reads and decodes the stored JSON. It handles specific error cases like
errSecInteractionNotAllowedto distinguish between a missing grant and a locked keychain. Sources: Sources/Spotty/Spotify/KeychainManager.swift:39-50, Sources/Spotty/Spotify/KeychainManager.swift:110-120 - Clear: Deletes the token item from the keychain and clears associated browser cookies. Sources: Sources/Spotty/Spotify/KeychainManager.swift:57-60, Tests/SpottyBoundaryTests/AuthAndParsingChecks.swift:250-260
Error Handling & Reauthentication
The system classifies authentication errors to determine if a failure is transient or fatal. Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:219-225
Error Classification
| Error Code | Meaning | Action |
|---|---|---|
invalid_grant | The refresh token is revoked or expired (e.g., 6-month policy). | Fatal: Discard grant and force sign-in. |
state_mismatch | The redirect state does not match the request state. | Fatal: Reject authorization code. |
token_exchange_failed | Network error or 500-series status from Spotify. | Transient: Keep grant and retry later. |
malformed_response | The API returned unreadable JSON. | Transient: Log error; do not discard session. |
Sources: Sources/Spotty/Spotify/KeymasterAuth.swift:93-110, Sources/Spotty/Spotify/KeymasterAuth.swift:233-245
Reauthentication Marker
If a playback session is rejected by Spotify's accesspoint despite a valid Web API grant, the app sets the requiresReauthentication flag. This prevents the app from attempting to restore the known-rejected streaming session on the next launch until the user explicitly re-authorizes. Sources: Tests/SpottyBoundaryTests/AccountReauthenticationPersistenceChecks.swift:71-85, Tests/SpottyBoundaryTests/AccountReauthenticationPersistenceChecks.swift:170-185
Cookie Cleanup
During sign-out, Spotty performs a targeted cleanup of the URLSession cookie store. It specifically deletes cookies from the spotify.com and accounts.spotify.com domains (e.g., sp_dc, sp_key) while leaving cookies for other domains intact. This ensures that the browser-based OAuth flow starts from a clean state for the next user. Sources: PRIVACY.md:65-72, Tests/SpottyBoundaryTests/AuthAndParsingChecks.swift:230-245
Summary: Authentication in Spotty is a localized PKCE-based system that prioritizes security by keeping credentials in the macOS Keychain and session safety by carefully managing token rotation and reauthentication states.