-
Notifications
You must be signed in to change notification settings - Fork 706
Reverse engineering
Some parts of this library are based on reverse engineering the Spotify protocol. Sometimes this breaks, and the developers have to fix it. This page talks about some tips and tricks for that
As of 2025, the Spotify web player has additional functionality beyond the official Spotify Web API. Much of this functionality is gated behind special access tokens that are provided to the web player. These have additional permissions beyond the regular Spotify Web API access tokens, but the process for obtaining them is obfuscated.
The current method uses TOTP with "secrets" that change over time. These "secrets" need to be retrieved from the web player then added to librespot. Here is one way of doing this via a browser debugger:
You can find the secret by setting a breakpoint in the debugger directly before the TOTP function is called. Search in the source code for totpServer
, set a breakpoint just before the definition of totp
, which is just before totpServer
, and work backwards. Take this opportunity to evaluate totpVer
as well, to check that it's the version you're expecting.
- set a breakpoint just before the relevant point of interest
- reload the page to hit this earlier breakpoint
- evaluate some subexpressions to figure out which earlier parts to set more breakpoints at
- rinse and repeat
You are looking for a TOTP function call that looks something like obj.generate(timestamp)
. obj
is the TOTP object and obj.secret.bytes
is the (final, "transformed") secret. In the source code, it is likely obfuscated e.g. Qe[l(0, 0, 483, 514)](p)
, but you can evaluate subexpressions in the debugging console to see that they match the above e.g. l(0, 0, 483, 514)]
evaluates to "generate".
Tip
Modern browser debuggers should be able to let you view the beautified version of any minified JS, as well as "go to definition" on any function that is in scope (e.g. when stopped at a breakpoint). For example, in Firefox I can't do this in the source code viewer, but I can do this by inputting the function name into the JS console, then the output will have a button I can click to jump to the definition in the source code.
It's best to get the secret just before the TOTP is generated, because Spotify does extra obfuscation (that is entirely irrelevant and pointless) to the hardcoded version of the "secret" before actually passing it to the TOTP module. The TOTP object will contain the final secret just before generation, and it will look something like:
>>> Ze
Object { issuer: "", label: "OTPAuth", issuerInLabel: true, secret: {…}, algorithm: "SHA1", digits: 6, period: 30 }
algorithm: "SHA1"
digits: 6
issuer: ""
issuerInLabel: true
label: "OTPAuth"
period: 30
secret: Object { bytes: Uint8Array(49) }
<prototype>: Object { … }
>>> Ze.secret.bytes.join(", ")
"57, 56, 57, 49, 53, 56, 53, 51, 55, 56, 56, 51, 56, 56, 54, 53, 56, 52, 56, 49, 53, 57, 55, 51, 51, 55, 51, 54, 53, 55, 54, 55, 55, 49, 49, 48, 55, 53, 48, 49, 49, 50, 56, 48, 49, 49, 55, 54, 49"
You can test your output by modifying this script. Note that this script uses the pre-obfuscated secret, so you'll have to modify it slightly like so:
secret = [..] # in the format above, pasted from the browser console
secret32 = base64.b32encode(bytes(secret)).decode().rstrip("=")
pyotp.TOTP(secret32, digits=6, interval=30).at(int(time.time())) # use server_time if available, but can test without it
If they change algorithm away from TOTP, then the above method won't work as-is, although its principles will still apply. You can get relevant starting strings to search for, by setting XHR breakpoints for any network requests that seem like they are grabbing an access token.
More discussion in #1475.