adamgrieger / spotify-web-api-ts

An isomorphic TypeScript wrapper for Spotify's Web API
https://adamgrieger.github.io/spotify-web-api-ts/
MIT License
37 stars 10 forks source link

Add access token example to README #13

Open patrick-motard opened 4 years ago

patrick-motard commented 4 years ago

The README's example says:

const spotify = new SpotifyWebApi({ accessToken: '<YOUR_ACCESS_TOKEN_HERE>' });

It would be useful to know how to generate that access token.

adamgrieger commented 4 years ago

I agree. I still need to finish up documentation for some endpoints and especially authorization workflows. I've been a bit burnt out as of late, but it's definitely something I want to get done.

patrick-motard commented 4 years ago

@adamgrieger I completely understand. You've done so much great work here. It's important to take breaks! I would be more than happy to add examples. I'll admit I haven't taken the time to read through the code and see if there are methods to an auth url similar to the createAuthorizeURL method in spotify-web-api-node. I will get a PR cooked up here in this next week or so for this. Thank you for your prompt reply!

adamgrieger commented 4 years ago

That'd be great! All of the authorization stuff is located here if you're curious.

patrick-motard commented 3 years ago

Still trying to get authentication working. I swapped out the spotify-web-api-node client with this one, and replaced all the different function calls that were different between the two. I can interact with spotify just fine when I don't change the scopes. It doesn't ask me to authorize the client. I think that's because it's re-using an existing auth/refresh token. However, when I add an additional scope that I haven't authorized yet, it loads the page that asks for my permission to grant the app access to spotify with the additional scope. I click accept, which results in a webpage that says "Error Oops! Something went wrong, please try again or check out our help area." which i believe corresponds to the GET https://accounts.spotify.com/authorize/accept 405 I'm seeing in the console.

Out of curiousity, i tried navigating to the authorization url in my browser, which brings up the same permissions prompt. When I click on the accept button it tries to redirect me to my app, which fails since the app isn't running. However... when i open the app, with the same additional scope that I just approved through the browser, it authenticates right away and works with the new scope. No prompt for granting permissions to the app. Just goes straight to the app.

I don't want to run out of scopes to test this bug with, so I'm not going to try the browser steps again. I'm really baffled by this. It's not redirecting to my app when i click accept in my electron app. Worked perfectly fine when i used spotify-web-api-node, so i must be missing something. I've just been smashing my head against this problem on and off for weeks and am really starting to lose steam. I don't know what to try next.

adamgrieger commented 3 years ago

I think the way that I implemented authorization isn't 100% identical to spotify-web-api-node. I would have to look at it again since it's been a while (assuming Spotify didn't change their authorization API). I can try to get some docs written for the authorization techniques that I've implemented so far, because I know that's probably the most confusing part of the library. Out of curiosity, which authorization workflow are you using?

patrick-motard commented 3 years ago

I believe Authorization Code flow. Hopefully that answers your question? I'm using GetRefreshableAuthorizationUrl https://github.com/adamgrieger/spotify-web-api-ts/blob/aaa78bc5340f4094c806846d04b36e9cf8322fab/src/index.ts#L108.

I construct that url, and render it in electron. That loads the page from spotify that asks for granting permission to the app. When I click accept, I expect it to redirect to the redirect uri in the auth url. The redirect uri is a localhost express app running in my electron app that renders my electron app when it's hit. However, the redirect never happens. The 'will-navigate' event is fired instead of 'will-redirect', and the page that shows after 'accept' is clicked is a spotify error page, with the 405 error mentioned above.

Another interesting difference is that in spotify-web-api-node it would first have me log in to spotify. This library never has me log in to spotify. Somehow it just knows it's my account that's being accessed. Maybe spotify-web-api-node uses Implicit Grant and/or Client Credentials flows? I really need to read up on these to understand the difference.

adamgrieger commented 3 years ago

Interesting. I'll try to take a look this weekend. I can prioritize writing an example for the Authorization Code flow.

adamgrieger commented 3 years ago

I just tried a minimal example locally using the Authorization Code workflow and it works for me still. I can add a fleshed-out examples directory and update the README.md. I'm using express too but not within an Electron app. I haven't used Electron so I can't comment on whether there might be an issue there. I can leave some example server code here in the meantime. On the client-side, I used localStorage to store the access_token in the redirect URI and then passed it into a SpotifyWebApi instance after redirecting.

import express from "express";
import { SpotifyWebApi } from "spotify-web-api-ts";

const app = express();
const port = 3000;

const spotify = new SpotifyWebApi({
  clientId: "<CLIENT_ID>",
  clientSecret: "<CLIENT_SECRET>",
  redirectUri: "http://localhost:3000/callback.html",
});

app.use(express.static("public"));

app.get("/tempAuthUrl", (req, res) =>
  res.send(
    spotify.getTemporaryAuthorizationUrl({ scope: ["user-library-read"] })
  )
);

app.listen(port, () =>
  console.log(`Example app listening at http://localhost:${port}`)
);
patrick-motard commented 3 years ago

@adamgrieger are you able to able to test this with a scope that you haven't already granted previously? This auth flow works fine for me if i use scopes I have already approved.

With either scopes you have already approved, or havent approved, do you get a webpage that presents you with spotify permissions you have to approve? If so, what happens when you click accept?

patrick-motard commented 3 years ago

I'm also not able to use http://localhost:8888/callback.html as a redirect url, i get the error: INVALID_CLIENT: Invalid redirect URI. http://localhost:8888/callback works fine though (with scopes i have already authorized)

patrick-motard commented 3 years ago

Also, getTemporaryAuthorizationUrl does not work for me at all. Only getRefreshableAuthorizationUrl works for me.

patrick-motard commented 3 years ago

The answer to my woes, I believe, is in the status code. GET https://accounts.spotify.com/authorize/accept 405 is the issue. When clicking the accept button in the browser, the browser sends a POST to https://accounts.spotify.com/authorize/accept. No 405. 405 is the wrong method status code. Makes sense. For some reason my electron app is sending a get request to Spotify instead of a post. I'm looking into why this is happening.

patrick-motard commented 3 years ago

Alright. I've solved it. Since this took me so damn long I want to leave a trail of crumbs for any other poor sap like me that faces this issue. It was indeed because I was sending a GET request when it should have been a POST. As to why, it was completely by accident. In electron, you can intercept browser events server side. I was intercepting requests to localhost:8888/callback. When my app is redirected to that url, I want two things to happen:

I was doing this both in the will-redirect and will-navigate event handlers. Here is what the will-navigate handler looked like, that was causing trouble:

  win.webContents.on('will-navigate', (event, url) => {
    if (!url.includes('http://localhost')) {
      win.loadURL(url);
    }
  });

Turns out that this if condition was true for the url https://accounts.spotify.com/authorize/accept. The thinking when I wrote this was "anything that isn't localhost should be handled via win.loadURL. If the browser wants to make a request, make a request. But for localhost, do nothing, so that the re-direct event is triggered". I'm not actually sure if the 'will-navigate' event is triggered prior to a 'will-redirect' event, but I do know (now), that loadURL defaults to sending a GET request, unless a post payload is passed in as an additional parameter.

A simple conditional solved the issue.

  win.webContents.on('will-navigate', (event, url) => {
    if (url.includes('https://accounts.spotify.com/authorize/accept')) {
      return;
    }
    if (!url.includes('http://localhost')) {
      win.loadURL(url);
    }
  });

With this, I am exiting the handler for the url that is meant to be sent as a POST so that Electron (or the renderer?) doesn't get it's verb overwritten by my dumbass self. In the near future I will address whether or not the localhost conditional is needed at all. For now I just want to enjoy that this finally works again. And also kick myself.

I really appreciate your help @adamgrieger , especially considering your code wasn't the issue.