Creating a React App for Spotify Authentication: How to Implement Login Flow with Flask Backend?

72 views Asked by At

I am trying to authenticate user authentication through spotipy in my flask backend, which was working by itself. But when I try to introduce the front end components, it seems like the backend isn't sending the correct information OR the frontend is getting the wrong information. I tried making sure that I was sending cookies correctly from my backend, and that my cache wasn't being used to memorize whether a user was or wasn't logged in. I am very new to Web dev, especially front end so any help would be greatly appreciated.

Here is what I am expecting to happen with my code

  1. Check for a cached token or user authentication state on load.
  2. Redirect the user to the Spotify login if not authenticated.
  3. Handle the callback from Spotify and check the authentication state.
  4. Redirect back to localhost:3000/ where my frontend is hosted so it can check again, this time supposedly showing that the user has been logged in.

Here is the relevant code on my backend:

@app.route('/')
def index():
    cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
    auth_manager = spotipy.oauth2.SpotifyOAuth(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, redirect_uri=REDIRECT_URI, scope='user-read-currently-playing',
                                               cache_handler=cache_handler)
    

    if not auth_manager.validate_token(cache_handler.get_cached_token()):
        auth_url = auth_manager.get_authorize_url()
        #print(auth_url)
        return jsonify({"logged_in": False, "auth_url": auth_url})

    spotify = spotipy.Spotify(auth_manager=auth_manager)
    user_info = spotify.me()
    return jsonify({
        "logged_in": True,
        "user_info": {
            "display_name": user_info["display_name"],
            "id": user_info["id"],
            "uri": user_info["uri"],
            "profile_url": user_info["external_urls"]["spotify"]
        }
    })

@app.route('/callback')
def callback():
    code = request.args.get('code')
    print(code)
    if code:
        cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
        auth_manager = spotipy.oauth2.SpotifyOAuth(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, redirect_uri=REDIRECT_URI, scope='user-read-currently-playing',
                                                   cache_handler=cache_handler)
        auth_manager.get_access_token(code)
        session.modified = True
        response = make_response(redirect('http://localhost:3000/'))
        return response
    return 'Missing code parameter. Please try again.', 400

And here is my App.js on the frontend:

function App() {
  const [message, setMessage] = useState('');
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [authUrl, setAuthUrl] = useState('');
  
  useEffect(() => {
    const url = `http://127.0.0.1:5000/?t=${Date.now()}`;
    axios.get(url, { withCredentials: true })
      .then(response => { 
        const { logged_in, auth_url } = response.data;
        console.log(response.data);
        setIsLoggedIn(logged_in);
        if (!logged_in) {
          setAuthUrl(auth_url);
          console.log('Redirecting to:', auth_url);
        }
      })  
      .catch(error => console.error('Error:', error));
  }, []);

  return (
    <Router>
      <div className="App">
        <div className="App-header">
          <Routes>
            <Route path="/login" element={!isLoggedIn ? <Login authUrl={authUrl} /> : <Navigate to="/" />} />  // Use the Login component here
            <Route path="/" element={isLoggedIn ? <SearchBar onSearch={setMessage} /> : <Navigate to="/login" />} />    
          </Routes>
        </div>
      </div>
    </Router>
  );
}
export default App;

I did some testing, and in the callback function, if I redirect just back to localhost:5000, it will show me this data:

    "logged_in": True,
    "user_info": { etc etc...
        

Which is what I want the front end to detect. But for some reason it still picks up logged_in as false.

1

There are 1 answers

17
Bench Vue On

When utilizing spotipy for the authorization code flow, you don't necessarily need to handle the callback endpoint with redirection yourself.

Spotipy supports several authentication flows provided by Spotify, including the Authorization Code Flow.

When Spotipy is described as middleware for the Authorization Code Flow, it means that Spotipy can handle much of the authentication process seamlessly within your Python application.

Spotipy is middleware. So you don't necessary to handle ./callback endpoint.

Demo

Back-end server by Flask

import spotipy

from flask import Flask, session, jsonify
from flask_cookie_decode import CookieDecode
from spotipy.oauth2 import SpotifyOAuth
from flask_cors import CORS, cross_origin

CLIENT_ID = '<your client id>'
CLIENT_SECRET = '<your client secret>'
REDIRECT_URI = '<your redirect uri>' # get from developer dash board
username = '<your Spotify User id>'  # get from user profile
SCOPEs = ['user-read-currently-playing', 'user-read-recently-played']

# Create the Flask application
app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'

app.config.update({'SECRET_KEY': 'MY_SECRET_KEY'})
cookie = CookieDecode()
cookie.init_app(app)

cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
auth_manager = SpotifyOAuth(client_id=CLIENT_ID,
                            client_secret=CLIENT_SECRET,
                            redirect_uri=REDIRECT_URI,
                            scope=SCOPEs,
                            username=username,
                            cache_handler=cache_handler)
auth_url = auth_manager.get_authorize_url()

@app.route('/get_logged')
@cross_origin()
def get_session():
    if (session and session['user']):
        return jsonify(dict(session))
    else:
        return jsonify({'logged_in': False, 'auth_url': auth_url})

@app.route('/clear_session')
@cross_origin()
def clear_session():
    session.clear()
    return jsonify({'session': 'cleared'})

@app.route('/login')
@cross_origin()
def login():
    auth_manager = SpotifyOAuth(client_id=CLIENT_ID,
                                client_secret=CLIENT_SECRET,
                                redirect_uri=REDIRECT_URI,
                                scope=SCOPEs,
                                username=username)
    if (session and session['user']):
        data = jsonify(dict(session).get('user')['user_info'])
        return data

    sp = spotipy.Spotify(auth_manager=auth_manager)
    user_info = sp.me()
    simple_user_info = {
        "logged_in": True,
        "user_info": {
            "display_name": user_info["display_name"],
            "id": user_info["id"],
            "uri": user_info["uri"],
            "profile_url": user_info["external_urls"]["spotify"]
        }
    }
    session['user'] = simple_user_info
    return jsonify(dict(session).get('user'))

if __name__ == '__main__':
    app.run()

API Testing by Postman

Clear Session

GET http://localhost:5000/clear_session

Get Logged In

GET http://localhost:5000/get_logged

Login

GET http://localhost:5000/login

Note- This API will only call by React

enter image description here

Front-end Code

App.js

import React, { useState } from 'react';
import axios from 'axios';

function App() {
  const [loggedIn, setLoggedIn] = useState(false);
  const [userData, setUserData] = useState(null);

  const handleLogin = async () => {
    try {
      const response = await axios.get('http://localhost:5000/login'); // Update URL if needed
      setUserData(response.data);
      setLoggedIn(true);
    } catch (error) {
      console.error('Error logging in:', error);
    }
  };

  return (
    <div>
      {loggedIn ? (
        <div>
          <h1>Welcome, {userData?.user_info?.display_name}</h1>
          <p>ID: {userData?.user_info?.id}</p>
          <p>URI: {userData?.user_info?.uri}</p>
          <a href={userData?.user_info?.profile_url}>Profile Link</a>
        </div>
      ) : (
        <div>
          <h1>Not logged in</h1>
          <button onClick={handleLogin}>Login with Spotify</button>
        </div>
      )}
    </div>
  );
}

export default App;

Result of Front-end

http://localhost:3000/

enter image description here

References

How to enable CORS in flask

Session Example in Flask

flask-cookie-decode

How to get client_id, client_secret and redirect_uri and user id

Current playlist with Spotipy


Step 1

import json
import spotipy
from spotipy.oauth2 import SpotifyOAuth

CLIENT_ID = '<your client id>'
CLIENT_SECRET = '<your client secret>'
REDIRECT_URI = '<your redirect uri>' # get from developer dash board
username = '<your id>'        # get from user profile
SCOPEs = ['user-read-currently-playing', 'user-read-recently-played']

auth_manager = SpotifyOAuth(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, redirect_uri=REDIRECT_URI, scope=SCOPEs, username=username)
auth_url = auth_manager.get_authorize_url()
print('auth_url: ' + auth_url)

sp = spotipy.Spotify(auth_manager=auth_manager)
token_info = sp.auth_manager.get_cached_token()
access_token = None
if (token_info is None):
    access_token = sp.auth_manager.get_access_token()
else:
    access_token = token_info['access_token']
print('access_token: ' + access_token)

user_info = sp.me()
simple_user_info = {
    "display_name": user_info["display_name"],
    "id": user_info["id"],
    "uri": user_info["uri"],
    "profile_url": user_info["external_urls"]["spotify"]
}
print('user_info: ' + json.dumps(simple_user_info, indent=4))

Step 1 result

enter image description here