Search code examples
pythondockerauthenticationspotifyspotipy

How to handle web based login from a docker container using python and flask?


I writing an application that collects some various data about the song I currently listen to on Spotify. To do this I use Python, flask and spotipy running inside a docker container. The thing is that Spotify's API requires some user input to login, and when calling the login API a web page is open up for the user to enter the user credentials. This has to be done only once per server (I think). However, inside a docker container (running on my Linux server), I can't do that. How do I solve a problem like this?

I have set the callback on Spotify's developer page and registered an application. Also I have set the following environment variables (not all is used :-P)

        self.client_id = os.getenv("CLIENT_ID")
        self.client_secret = os.getenv("CLIENT_SECRET")
        self.oauth_token = os.getenv("OAUTH_TOKEN")
        self.username = os.getenv("USERNAME")
        self.redirect_uri = os.getenv("REDIRECT_URI")
        self.scope = os.getenv("SCOPE") 

And basically using the following code to authorize and get the song data from Spotify

from spotipy.oauth2 import SpotifyOAuth

            self.auth_manager = SpotifyOAuth(
                                    client_id=self.client_id,
                                    client_secret=self.client_secret,
                                    scope=self.scope,
                                    redirect_uri = self.redirect_uri,
                                    username= self.username,
                                )

            self.spotify = spotipy.Spotify(auth_manager=self.auth_manager)
            response = self.spotify.currently_playing()


I have tried tried to solve this using a headless selenium and webdriver but haven't got it to work. I have also tried to ditch spotipy and side load a token from another instance and got it to work, but that is not a sustainable solution, since I need to be able to run a new docker container without any manual work. (And I have other APIs that also need this kind of solution)


Solution

  • There are many obstacles to running Spotify with Flask on Docker Container.

    Overview

    Overview

    #1 Local Flask Test

    #1.1 Flask code - save as login.py

    from flask import Flask, request, redirect
    from requests_oauthlib import OAuth2Session
    from requests.auth import HTTPBasicAuth
    import requests
    import os
    
    app = Flask(__name__)
    try:
        AUTH_URL=os.getenv("AUTH_URL")
        TOKEN_URL=os.getenv("TOKEN_URL")
        REDIRECT_URI=os.getenv("REDIRECT_URI")
        CLIENT_ID=os.getenv("CLIENT_ID")
        CLIENT_SECRET=os.getenv("CLIENT_SECRET")
        SCOPE=['user-read-currently-playing']
        # The checking environment variable is set
        print(AUTH_URL)
        print(TOKEN_URL)
    except KeyError: 
       print ('Please set the environment variable for Spotify')
    
    def get_headers(token):
        return {"Authorization": "Bearer " + token}
    
    @app.route("/")
    def root_message():
        app.logger.info('hello! root accessed')
        return 'I am a spotify server'
    
    @app.route("/login")
    def login():
        app.logger.info('login logged in successfully')
        spotify = OAuth2Session(CLIENT_ID, scope=SCOPE, redirect_uri=REDIRECT_URI)
        authorization_url, state = spotify.authorization_url(AUTH_URL)
        return redirect(authorization_url)
    
    # Your redirect URI's path
    # http://localhost:3000/callback?code=AQDTZDK66wl...se8A1YTe&state=kt4H....963Nd
    @app.route("/callback", methods=['GET'])
    def callback():
        # get access token
        code = request.args.get('code')
        resp = requests.post(TOKEN_URL,
            auth=HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET),
            data={
                'grant_type': 'authorization_code',
                'code': code,
                'redirect_uri': REDIRECT_URI
            })
        access_token = resp.json()['access_token']
    
        # get current playing
        headers = get_headers(access_token)
        result1 = requests.get(url='https://api.spotify.com/v1/me/player/currently-playing', headers=headers)
        current_song = result1.json()
        return current_song
    
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=3000,debug=True) # your redirect URI's port
    

    #1.2 Set environment variable

    • in Windows, if Linux/Mac use EXPORT instead of SET
    SET AUTH_URL=https://accounts.spotify.com/authorize
    SET TOKEN_URL=https://accounts.spotify.com/api/token
    SET CLIENT_ID=<your client id>
    SET CLIENT_SECRET=<your client secret>
    SET REDIRECT_URI=http://localhost:3000/callback <- <your redirect URL>
    

    #1.3 run it

    python login.py
    

    #1.4 access it and Spotify login with your credential

    localhost:3000/login
    

    Result - confirm local Flask is providing current play song

    enter image description here

    #2 Dockerfile

    #2.1 Save requirements.txt for python dependency install.

    click==8.1.3
    colorama==0.4.5
    Flask==2.2.2
    gunicorn==20.1.0
    itsdangerous==2.1.2
    Jinja2==3.1.2
    MarkupSafe==2.1.1
    python-dotenv==0.21.0
    requests-file==1.5.1
    requests-oauthlib==1.3.1
    requests-toolbelt==0.9.1
    requests==2.28.1
    Werkzeug==2.2.2
    

    #2.2 Dockerfile

    I am using python:3.10 but if you want to more small size use python:3.10-slim

    # start by pulling the python image
    FROM python:3.10
    
    # hardcoded envirenment - Causion no single quote or double quote
    ENV AUTH_URL=https://accounts.spotify.com/authorize
    ENV TOKEN_URL=https://accounts.spotify.com/api/token
    ENV CLIENT_ID=<your client id>
    ENV CLIENT_SECRET=<your client secret>
    ENV REDIRECT_URI=http://localhost:3000/callback <- <your redirect URI>
    
    # port expose to outside, your redirect URI's port
    EXPOSE 3000
    
    # copy the requirements file into the image
    COPY ./requirements.txt /app/requirements.txt
    
    # switch working directory
    WORKDIR /app
    
    # install the dependencies and packages in the requirements file
    RUN /usr/local/bin/python -m pip install --upgrade pip
    RUN pip install -r requirements.txt
    
    # copy every content from the local file to the image
    COPY . /app
    
    # configure the container to run in an executed manner
    ENTRYPOINT ["python"]
    
    CMD ["login.py"]
    

    #3 Docker image

    From the terminal, to create docker image

    $ docker image build -t local_spotify .
    

    Result enter image description here

    #4 Docker Container

    I use port 3000 but it should be change your redirect PORT

    docker run -p 3000:3000 --name spotify_login local_spotify
    

    Make sure the AUTH_URL and ENV TOKEN_URL were displayed

    enter image description here

    #5 Access Flask Server

    localhost:3000/login
    

    enter image description here

    debugging tips!

    To delete all of running container

    $ docker rm -f $(docker ps -aq)
    

    Can go inside container for looking environment variable.

    $ docker exec -it spotify_login bash
    
    root@<container id>:/app# env
    

    To delete docker image

    $ docker rmi -f local_spotify:latest