Closed wassay13 closed 5 months ago
It looks like you're on the right track.
The CORS has nothing to do with Spotify and has to be handled by FastAPI instead. You can read more about this here: https://fastapi.tiangolo.com/tutorial/cors/
Then the authentication process. I'd like you to take a look at this app.py example which shows how to get the authorise URL and how a callback from Spotify can be handled. This is a pretty good example, even though it is implemented in Flask. But there's not much difference to FastAPI in this case.
Admittedly, it's a bit tight, because the login process and the callback are implemented in the same function (and therefore the same endpoint).
But it's still possible to understand it. I'll explain it to you. The function is divided into four parts.
First, the cache_handler
and auth_manager
are created. They are not passed to spotipy.Spotify
yet, and this is important, mainly because of the auth manager, as we don't want to trigger the internal authentication process.
cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
auth_manager = spotipy.oauth2.SpotifyOAuth(scope='user-read-currently-playing playlist-modify-private',
cache_handler=cache_handler,
show_dialog=True)
As for the FlaskSessionCacheHandler
, I'm not sure if there is something similar for FastAPI. I've seen that you've created your own FastAPISessionCacheHandler
, and honestly, it's probably fine to start with. You just have to remember that the user can easily extract the token. They shouldn't be able to do much damage as the token is tied to their account anyway.
Then, in the second part, it checks if the incoming request is from a callback function by checking if "code" is one of the arguments used. The value of this argument is then checked for authenticity. If all is well, the user is logged in and the page is refreshed by redirecting to the same page.
if request.args.get("code"):
# Step 2. Being redirected from Spotify auth page
auth_manager.get_access_token(request.args.get("code"))
return redirect('/')
The third part is to check if the user isn't logged in. If that's the case, then an authorize url is generated and returned to the user as a link. Yes, I know the order of the parts is a bit confusing, but bear with me. There are also comments here and in part 2 to indicate that this step is the first to be run.
if not auth_manager.validate_token(cache_handler.get_cached_token()):
# Step 1. Display sign in link when no token
auth_url = auth_manager.get_authorize_url()
return f'<h2><a href="{auth_url}">Sign in</a></h2>'
And then the last part, where we finally get our spotipy.Spotify
object by passing our auth_manager
to it. Since we should already be logged in when we reach this part, there should be no prompt asking us to enter a link.
# Step 3. Signed in, display data
spotify = spotipy.Spotify(auth_manager=auth_manager)
return f'<h2>Hi {spotify.me()["display_name"]}, ' \
f'<small><a href="/sign_out">[sign out]<a/></small></h2>' \
f'<a href="/playlists">my playlists</a> | ' \
f'<a href="/currently_playing">currently playing</a> | ' \
f'<a href="/current_user">me</a>'
I'd recommend running this code on your machine, but be sure to read the comment at the top of the file. It contains some useful information.
I hope I was able to help you.
Hi Dieser, I tried the the exact same approach you suggest but its not working, heres why:
in get_access_token
we have
payload = {
"redirect_uri": self.redirect_uri,
"code": code, or self.get_auth_response(),
"grant_type": "authorization_code",
}
So when user hit '/'
endpoint to my backend (which is running on different server from frontend) for very first time, this function gets called: self.get_auth_response()
and the nature of this function is to open new window in computer (server) orr interactive terminal by following the flow it calls: _get_auth_response_interactive
in which it actually execute the logic.
def get_auth_response(self, open_browser=None):
logger.info('User authentication requires interaction with your '
'web browser. Once you enter your credentials and '
'give authorization, you will be redirected to '
'a url. Paste that url you were directed to to '
'complete the authorization.')
redirect_info = urlparse(self.redirect_uri)
redirect_host, redirect_port = get_host_port(redirect_info.netloc)
if open_browser is None:
open_browser = self.open_browser
if (
open_browser
and redirect_host in ("127.0.0.1", "localhost")
and redirect_info.scheme == "http"
):
# Only start a local http server if a port is specified
if redirect_port:
return self._get_auth_response_local_server(redirect_port)
else:
logger.warning('Using `%s` as redirect URI without a port. '
'Specify a port (e.g. `%s:8080`) to allow '
'automatic retrieval of authentication code '
'instead of having to copy and paste '
'the URL your browser is redirected to.',
redirect_host, redirect_host)
return self._get_auth_response_interactive(open_browser=open_browser)
def _get_auth_response_interactive(self, open_browser=False):
if open_browser:
self._open_auth_url()
prompt = "Enter the URL you were redirected to: "
else:
url = self.get_authorize_url()
prompt = (
"Go to the following URL: {}\n"
"Enter the URL you were redirected to: ".format(url)
)
response = self._get_user_input(prompt)
state, code = SpotifyOAuth.parse_auth_response_url(response)
if self.state is not None and self.state != state:
raise SpotifyStateError(self.state, state)
return code
___________________________________________________________________________________
TERMINAL:
(venv) say@ubuntu-xyz:~/app/src$ uvicorn --host 0.0.0.0 main:app
INFO: 137.59.220.8:12607 - "GET /spotify/ HTTP/1.1" 200 OK
INFO: 137.59.220.8:12614 - "GET /spotify/callback?code=AQBoiQiqufrNiKvstckYhncSoyA4DHA HTTP/1.1" 307 Temporary Redirect
Enter the URL you were redirected to:
My name is Niko actually, but that's all right.
I don't think that the relevant part is included in your snippet, so that's why I straight up built a little example with FastAPI. You said that you're using it as a Backend, so the auth url will be returned as a simple JSON and not as a 307 Temporary Redirect
or similar. Check it out: https://github.com/dieser-niko/spotipy-fastapi-oauth
Hey hey hey, ty Niko, it works. Although I've tried it manually which also working perfectly fine but I'll update my code with this approach so I don't fall under unexpected error in future.
Glad to hear that. If you want to, you can change to an authorization header instead of cookies. Probably the only thing I regret about my code :D
So I have build backend and frontend sapreatly and test them on localhost in which everything works perfectly fine, and because I'm using FastAPI for backend so I just build CacheHandler for it too:
But as soon as I deployed both on DigitalOcean VM I fall into lot of errors. (please look at return statement in root endpoint)
Till yet I've conclude two major scenarios: first that I cannot let user to authorize from my backend IP directly because of CORS. And second is that if I send auth_url and let frontend open it in new window then in my backend I'll get interactive terminal to enter url manually on behalf of them (what on earth?) also all the endpoints will get freeze till I don't stop server or put anything else.
Last Option:
Now I dig into Spotipy library to find out why I'm getting interactive terminal and find this:
So I thought why not just give the code manually and get rid of "or self.get_auth_response()" right? NO! But I tried:
This approach solve " Enter the URL you were redirected to: " issue but who knows what coming..
Then from my frontend I call another endpoint:
Then the endpoint I call:
Now till getting back access token everything seems working perfectly fine but when I call "store_user_data(db, user, access_token, sp)" this function, my code gets broke, ss attached below :(
This is the problematic function which works perfectly fine on localhost but since I changed code to give it manually I don't even know what's happening under the hood:
After digging more I get to know I broke hell lot of things (ig), and need to rebuild and adjust this library for my use case which I cannot as I'm still relatively new into coding and don't understand much but it'll be highly appreciated if someone guide me what to do because this is my first project which I started building on my own and not clone from some youtube video. And there is high chances that I feel I'm missing something very basic which cause me this issue (coding solely with help of AI made me feel like this). PS: And yeah I'm not giving the access token in "response = sp.current_user_followed_artists(limit=50, after=after)" because I don't understand where exactly it'll need to go, maybe in "self._get"?