librespot-org / librespot

Open Source Spotify client library
MIT License
4.7k stars 573 forks source link

Create credentials from access token #1098

Closed kingosticks closed 1 year ago

kingosticks commented 1 year ago

New API method and example relating to https://github.com/librespot-org/librespot/issues/1086

roderickvd commented 1 year ago

Nice addition. If you could resolve that conflict?

GiviMAD commented 1 year ago

The commercial sdk seems to allow this also: https://developer.spotify.com/documentation/commercial-hardware/implementation/reference/latest/#spconnectionloginoauthtoken

kingosticks commented 1 year ago

Will sort tonight. I can change with_password return type also, I had actually copy and pasted the signature from there to start with!

kingosticks commented 1 year ago

The commercial sdk seems to allow this also: https://developer.spotify.com/documentation/commercial-hardware/implementation/reference/latest/#spconnectionloginoauthtoken

Cool, thanks.

I am currently setting the username and librespot always uses it when authenticating but I was wondering if that's actually necessary for this login method. This example suggests it isn't.

kingosticks commented 1 year ago

It seems you can remove the username from the auth packet when using AUTHENTICATION_SPOTIFY_TOKEN and change the method to just with_access_token(token: string), which would be nice. And that means Credentials.username should really be an Option<String>...

But then I had some issues when trying to actually play tracks with my token based login. I am sure that was working fine yesterday so I must be doing something silly, it is late.

gdesmott commented 1 year ago

Agreed, it would be best to not rely on the username name here (assuming we can't easily extract it from the token?). That would save applications to ask for this username which is redudant when doing the proper OAuth flow.

kingosticks commented 1 year ago

I thought ouath access tokens were supposed to be opaque. I don't think we've any idea how to extract anything from them.

I just want to double check there's no difference between setting username and not setting username during authentication. I think my test last night must have been wonky. I was trying lots of things, including using a different client ID; although, I'm not sure why you'd want to given the default keymaster client IDs seem to work just fine.

gdesmott commented 1 year ago

although, I'm not sure why you'd want to given the default keymaster client IDs seem to work just fine.

Isn't each application supposed to use its own client id to respect the Spotify Developer Terms ?

GiviMAD commented 1 year ago
I thought ouath access tokens were supposed to be opaque. I don't think we've any idea how to extract anything from them.

@kingosticks, just as a comment, you can extract the username from the token by relying on the Spotify API, but you'll need token to have the user-read-email+user-read-private scopes for that, so maybe not a good idea to add that part.

But then I had some issues when trying to actually play tracks with my token based login. I am sure that was working fine yesterday so I must be doing something silly, it is late.

In case you are using the Web API, maybe you are facing this issue https://github.com/librespot-org/librespot/issues/1099 after rebasing the code to the dev branch

kingosticks commented 1 year ago

I assumed he meant extracting it from the token itself. Using the token to call the API and find the account info doesn't sound interesting.

Yes, it could be related to #1099. Hmm.

GiviMAD commented 1 year ago

Isn't each application supposed to use its own client id to respect the Spotify Developer Terms ?

About this, because I was already thinking on it, I think it will be nice to confirm if the version 0.4.2 witch relays always on the keymaster client ID can work after switching that by a personal application client id. As those client ids seems to be granted with all the scopes maybe it work, and I think it will be more respectful with their developer terms and probably more secure as they can rotate their client id in the future. I'll give it a try some day but in case someone has already tried it and can share his experience that will be nice.

Just to clarified it, I'm not proposing removing the current functionality of relaying on the official app client_id but to add an option to operate with a personal client_id (of course after verifying it works). WDYT?

kingosticks commented 1 year ago

OK, so the problem with playback when using access token authentication in the dev branch is real. Here's the log showing what went wrong:

[2023-01-15T23:56:38Z DEBUG librespot_playback::player] command=Load(SpotifyId("spotify:track:0OLBUtDuXJQq8N9T38r6W1"), true, 0)
[2023-01-15T23:56:38Z DEBUG librespot_playback::player] command=AddEventSender
[2023-01-15T23:56:38Z DEBUG librespot::component] new SpClient
[2023-01-15T23:56:38Z INFO  librespot_core::spclient] Resolved "gew1-spclient.spotify.com:443" as spclient access point
[2023-01-15T23:56:38Z DEBUG librespot::component] new TokenProvider
[2023-01-15T23:56:38Z TRACE librespot_core::token] Requested token in scopes "playlist-read" unavailable or expired, requesting new token.
[2023-01-15T23:56:38Z ERROR librespot_core::mercury] error 403 for uri hm://keymaster/token/authenticated?scope=playlist-read&client_id=65b708073fc0480ea92a077233ca87bd&device_id=5c28e50f-01cf-46cc-a435-3992a039da16
[2023-01-15T23:56:38Z DEBUG librespot_core::session] could not dispatch command: Service unavailable { error handling Mercury response: MercuryResponse { uri: "hm://keymaster/token/authenticated?scope=playlist-read&client_id=65b708073fc0480ea92a077233ca87bd&device_id=5c28e50f-01cf-46cc-a435-3992a039da16", status_code: 403, payload: [[123, 34, 99, 111, 100, 101, 34, 58, 52, 44, 34, 101, 114, 114, 111, 114, 68, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 34, 73, 110, 118, 97, 108, 105, 100, 32, 114, 101, 113, 117, 101, 115, 116, 34, 125]] } }
[2023-01-15T23:56:38Z ERROR librespot_playback::player] Unable to load audio item: Error { kind: Unavailable, error: Response(MercuryResponse { uri: "hm://keymaster/token/authenticated?scope=playlist-read&client_id=65b708073fc0480ea92a077233ca87bd&device_id=5c28e50f-01cf-46cc-a435-3992a039da16", status_code: 403, payload: [[123, 34, 99, 111, 100, 101, 34, 58, 52, 44, 34, 101, 114, 114, 111, 114, 68, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 34, 73, 110, 118, 97, 108, 105, 100, 32, 114, 101, 113, 117, 101, 115, 116, 34, 125]] }) }
[2023-01-15T23:56:38Z ERROR librespot_playback::player] Skipping to next track, unable to load track <SpotifyId("spotify:track:0OLBUtDuXJQq8N9T38r6W1")>: ()

Compared with the regular method using user/pass authentication:

[2023-01-15T23:58:19Z DEBUG librespot_playback::player] command=Load(SpotifyId("spotify:track:0OLBUtDuXJQq8N9T38r6W1"), true, 0)
[2023-01-15T23:58:19Z DEBUG librespot_playback::player] command=AddEventSender
[2023-01-15T23:58:19Z DEBUG librespot::component] new SpClient
[2023-01-15T23:58:19Z INFO  librespot_core::spclient] Resolved "gew1-spclient.spotify.com:443" as spclient access point
[2023-01-15T23:58:19Z DEBUG librespot::component] new TokenProvider
[2023-01-15T23:58:19Z TRACE librespot_core::token] Requested token in scopes "playlist-read" unavailable or expired, requesting new token.
[2023-01-15T23:58:19Z TRACE librespot_core::token] Got token: Token {
        access_token: "XXXXX",
        expires_in: 3600s,
        token_type: "Bearer",
        scopes: [
            "playlist-read",
        ],
        timestamp: Instant {
            tv_sec: 3935,
            tv_nsec: 685210861,
        },
    }
[2023-01-15T23:58:19Z DEBUG librespot_core::spclient] Client token unavailable or expired, requesting new token.
[2023-01-15T23:58:19Z DEBUG librespot_core::http_client] Requesting https://clienttoken.spotify.com/v1/clienttoken
[2023-01-15T23:58:19Z TRACE librespot_core::spclient] Got client token: token: "AADaTnaWqjHsoejONXbmqZO3ScJoLJw1AV0KcNVdfIYQcambjTHhVPmsyyyWEJB1YEpd12H6UxNCgEsU3n/pyCKUWkBTd/v37CUwNcfVrDkaLfMMSVEpAaKui34oycX+rcaJhjKE/jl5V5yo6bMMMx4somCNLJcTZB+BhFSl8BjTyHIAP534yuvhX3uh6OAWDJEIKvkShogsKaO3xoM9kLKH4QoN0lZ4XJSTqdshqLWstzW5m+1K1w2DsVMbsZHusAwGF2ZKTBovwWtWKt4EwJyPX3Fs8bnO3I1vgA==" expires_after_seconds: 1216800 refresh_after_seconds: 1209600 domains {domain: "spotify.com"}
[2023-01-15T23:58:19Z DEBUG librespot_core::http_client] Requesting https://gew1-spclient.spotify.com:443/metadata/4/track/1ae7887f5a644395a8ab88b09e6e96ad?product=0&country=GB&salt=1702194571

I guess this makes sense from an OAuth point of view: if you've provided a token with certain permissions (scopes), it shouldn't be possible for it to request another token with extra permissions set. So the session you get with token-based auth isn't exactly the same as the session you get with user/pass-based auth. I don't think we realised this and I'm still trying to see if this is reflected anywhere in the login response.

I tried again with another token which had "playlist-read" granted in an effort to stop it requesting the new token. But that failed because TokenProvider doesn't know about the pre-existing token. Maybe that's easily fixable.

GiviMAD commented 1 year ago

@kingosticks when I used Librespot 0.4.2 authenticated with the mentioned spotify-connect tool using the default-token method, which just sends an access token with just the streaming scope but inside the blob (which should made no difference at the end), I don't think I have any error at all, so I'm not sure that your analysis is correct.

kingosticks commented 1 year ago

I was specifically saying that token-based auth ("access or "default" is irrelevant to librespot, as you say) does not work in the dev branch.

0.4.2 and dev are very different. The initial login is the same but when it comes to playback, they work differently. 0.4.2 gets audio data the traditional way via Hermes/Mercury and there's no problem using OAuth login with that. The dev branch gets audio data from Spotify's CDN via HTTP, to do that it first needs to get an OAuth token and then use that to get another token ("clienttoken") for CDN access. There seems to be a problem getting that OAuth token if you originally logged in using an OAuth token.

However, I expect you can force the same problem on 0.4.2 if you login using an OAuth token and then try to request a new token. Which you would presumably eventually want to do, since each token has a limited lifetime.

GiviMAD commented 1 year ago

0.4.2 and dev are very different. The initial login is the same but when it comes to playback, they work differently. 0.4.2 gets audio data the traditional way from Hermes and there's no problem there. The dev branch tries to first get an access token and then uses that to get a clienttoken for to accessing audio data from Spotify's HTTP CDN. There is a problem getting that access token.

Oh, I didn't have that context and I wasn't able to understand why the previous version works perfect just with the streaming scope, but make sense, thank you so much for the explanation. Probably is also the origin of the mentioned issue.

roderickvd commented 1 year ago

Following this -- let me know when you've got something ready to go. In the meantime I will chime in with advice if I have any.

kingosticks commented 1 year ago

I think I'm stuck. If we can't get a token when using this login method, how can we use the CDN? There must be a trick I'm missing. Any experience with 403 from keymaster ?

roderickvd commented 1 year ago

The web player probably uses the playplay HTTP endpoint to get encryption keys, so might not use Mercury anymore at all.

kingosticks commented 1 year ago

Yes. I guess if you're only using Mercury (old connect h/w sdk stuff??), it doesn't matter that you can't get a bearer token for the http api. But if you're using the http api (everything newer) and you need a bearer token, in theory you don't need Mercury at all; you've got login5 & clienttoken, the websocket, and spclient.

Assuming that's right, this stuff needs login5 to work to be useful in dev. Otherwise we can only login and not stream, which is pointless. It would be useful as is for 0.4, but of course we're done with that branch. So it makes more sense to help get login5 working. I remember there was a person doing it but I can't find their work anywhere.

In the process of trying to get this working I've spent a lot of time getting back up to speed with how things work. I've got some notes on what the current Windows app is doing, stuff many people already know but I was thinking I could write it up and update the wiki notes for those that don't.

Few things I don't understand:

  1. the app still opens a TCP socket to ap-gewX.spotify.com:4070, presumably for Hermes. What does it still need that for? How are people intercepting that alongside mitmproxy for http? I thought the old Hermes dumping method was broken.
  2. Why do they need a websocket and spclient? Wouldn't the websocket alone be enough? Just Spotify's technical debt?
roderickvd commented 1 year ago

Assuming that's right, this stuff needs login5 to work to be useful in dev. Otherwise we can only login and not stream, which is pointless. It would be useful as is for 0.4, but of course we're done with that branch. So it makes more sense to help get login5 working. I remember there was a person doing it but I can't find their work anywhere.

I think someone on Gitter was talking about it, but did not latch onto my proposal to make it a PR.

gdesmott commented 1 year ago

@GiviMAD @kingosticks : hey! Did you make any progress on this by any chance? :) I'm still blocked by the lack of proper oauth support as well.

kingosticks commented 1 year ago

For dev branch (audio data from the CDN), no. It's blocked on our missing login5 implementation, at least. I'd like to work on resolving that but it's low on my list right now and I'm not currently working on it.

gdesmott commented 1 year ago

What's login5 exactly and how is it needed here?

As I said here I managed to get streaming working so it's not clear to me what's missing exactly.

kingosticks commented 1 year ago

Login5 is a newer version of Spotify's auth. It's what their official clients currently use.

Correct me if I am wrong, but you found it working in the 0.4x branch, not the dev branch. I've edited what I originally said at https://github.com/librespot-org/librespot/pull/1098#issuecomment-1383859911 to hopefully be clearer.

gdesmott commented 1 year ago

Correct me if I am wrong, but you found it working in the 0.4x branch, not the dev branch. I've edited what I originally said at #1098 (comment) to hopefully be clearer.

It's been a while but I'm pretty sure I was testing with dev as all my stuffs depend on it (for lyrics).

kingosticks commented 1 year ago

I assumed you were using v0.4 since your comment referred to testing with the GStreamer element. Was that assumption wrong? It would be great if my unsuccessful experiments here using dev were incorrect. If you want to share your working patch I'll happily give it a try.

gdesmott commented 1 year ago

I just re-tested and you're right indeed. I can re-use the token when using librespost 0.4 but it does not work with dev unfortunately. :(

roderickvd commented 1 year ago

What do we want to do with this one? Do we believe we can get this working or shall we unfortunately close?

kingosticks commented 1 year ago

Sadly have to close for how I think. And then try again when we have login5 support.

kingosticks commented 1 month ago

@gdesmott it looks like Spotify have sorted out whatever the problem was here. We should be able to implement this now, in fact we have to as user+pass support is now broken!

3052 commented 1 month ago

we have to as user+pass support is now broken!

its working fine, I have code from March that works perfectly

gdesmott commented 1 month ago

its working fine, I have code from March that works perfectly

Same, it works fine here using 299b7dec20b45b9fa19a4a46252079e8a8b7a8ba

kingosticks commented 1 month ago

They broke it for a day, it's (EDIT: they) fixed now. There're many issues/posts in each of the various librespot implementations that don't use login5 about this. And if you were using cached re-usable credentials you would never have noticed.

What I don't know is if they've rolled back all changes i.e. did they leave Hermes login via Spotify token now working or re-break it. It would be nice to have that alternative auth option.

nullr0ute commented 1 month ago

Does it make sense to tag a release with all the auth fixes if it's breaking to ensure people that use tagged releases are aware if there's breakage/changes needed there?

kingosticks commented 1 month ago

Spotify fixed it themselves, you should not need a new release.

3052 commented 1 month ago

What I don't know is if they've rolled back all changes i.e. did they leave Hermes login via Spotify token now working or re-break it. It would be nice to have that alternative auth option.

@kingosticks Hermes isn't (as you know) even HTTP, so in my view it should only be used if its the only option, which in this case its not since login5 exists. I would strongly recommend moving to login5 if its not already done