-1

I have a Python Flask app running successfully on Heroku.

  1. The user is redirected to a login page for spotify Oauth
@bp.route('/api/spotify_auth_request')
@limiter.limit("2 per day")
def api_spotify_auth_request():
    """
    Requests authorisation from the Spotify API.
    """
    from uuid import uuid4

    base_url = "https://accounts.spotify.com/authorize/?"

    params = {
        "client_id": os.environ.get('SPOTIFY_CLIENT_ID'),
        "response_type": "code",
        "redirect_uri": "https://[REMOVED]/api/spotify_auth",
        "state": str(uuid4())
    }

    Spotify.valid_states.append(params["state"])
    url = base_url + urlencode(params)

    return redirect(url, 302)

Note: the state is added to Spotify.valid_states

class Spotify:
    valid_states = []
  1. After logging in, Spotify redirects the user to my app on route /api/spotify_auth
@bp.route('/api/spotify_auth')
@limiter.exempt
def api_spotify_auth():
    """
    Redirect endpoint from Spotify after authentication attempt.
    """
    code = request.args.get("code", None)
    error = request.args.get("error", None)
    state = request.args.get("state")

    if error:
        return error

    if state not in Spotify.valid_states:
        return jsonify({
            "error": "State returned was not valid",
            "state": state,
            "valid_states": Spotify.valid_states
        }), 500

    Spotify.valid_states.remove(state)

    # ... some more code

The issue is that upon Spotify redirecting to /api/spotify_auth the Spotify.valid_states has not updated with the original request. After a few refreshes the variable will update and the user will be allowed to progress.

I tried adding a 20s timeout before checking Spotify.valid_states but it seems the tab must be refreshed to update the variable.

I don't really want to disregard the state, are there any other solutions to ensuring the valid_states variable updates immediately after adding a new state?

XDGFX
  • 41
  • 2
  • 9

1 Answers1

0

Storing states like this is not threadsafe:

class Spotify:
    valid_states = []

If launched with several syncronous workers, each worker has its own memory which would explain the behaviour you are seeing ("After a few refreshes the variable will update and the user will be allowed to progress.").

As I discussed previously gunicorn on Heroku defaults to 2 syncronous workers, if no worker type or count is specified. A quick fix would be to specify 1 (syncronous) worker, although this may not cope well under load.

A better solution would be to implement a different storage backend to allow you to maintain a state, independent of the worker. Redis, which is offically supported by Heroku may be worth investigating for this.

v25
  • 7,096
  • 2
  • 20
  • 36