Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for total headless mode by instructing the user to open the URL in a browser. #528

Merged
merged 5 commits into from
Jul 8, 2020

Conversation

foobuzz
Copy link
Contributor

@foobuzz foobuzz commented Jul 6, 2020

This adds a new option open_browser in get_auth_response which, if False, will simply print the authorization URL and ask the user to visit it and paste the URL is has been redirected to (existing interactive way to get the authorization code). This allows the setup of Spotipy on a remote machine (e.g. Raspberry Pi or VPS) in a fully headless way.

open_browser defaults to True for backward compatibility.

Copy link
Member

@stephanebruckert stephanebruckert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one. Just left a comment about something I am not sure about.

Also can you please add a quick entry to the CHANGELOG?

Comment on lines +378 to +386
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)
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this condition? Aren't both sides doing the same thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case where we want to open the browser, the method _open_auth_url will launch the browser with the authorization URL. Otherwise, this authorization URL is printed out to stdout and the user needs to figure out how to open it by himself. In both cases we ask the user for the final URL she has been redirected to.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sure, I just realised this not only adds an option to force pasting the resulting URL, but it also prints the initial URL 👍

@nleroy917
Copy link
Contributor

I'm jumping on here because headless authentication with Spotify's API has been something I've been trying to get down for awhile... Is this PR really introducing total headless authentication? In both cases, user input is required. A truly headless authentication flow would be completely automated, and this is very difficult to do since Spotify's OAuth flow requires a log in with a browser. Thus, some sort of browser driver (like Selenium) is required to submit credentials and literally "click" the submit button on the page.

As far as I know, all but the Client Credentials Authorization Flow require some sort of user interaction - which must be automated via browser driver to truly become headless

@stephanebruckert
Copy link
Member

@nleroy917 you are right, this is more of a "browserless" mode, or at least opening a browser on the same machine is not being forced

@nleroy917
Copy link
Contributor

I wonder if a headless mode could ever be implemented without compromising the scale of spotipy. It’s advertised as “A light weight Python library for the Spotify Web API”, but almost all browser drivers I see that have javascript capabilities require some external dependencies like chromium or webkit... they are usually heavy like Selenium. This isn’t really in the spirit of spotipy.

I’ve found a few options:

  • splinter
  • Ghost.py
  • phantompy
  • mechanicalsoup

Some are deprecated, though, and others are hard to work with.

@foobuzz
Copy link
Contributor Author

foobuzz commented Jul 7, 2020

Well it all comes down to a matter of terminology anyway, but I still think that "headless" is a proper description whereas "browserless" is not. Headless merely means that it doesn't use a graphical interface, which is the case here since all the program uses is stdout and stdin. "Browserless" would misleading since the user still has to open the URL in a browser somehow. I've updated the changelog anyway to something more factual to resolve any ambiguity.

@foobuzz
Copy link
Contributor Author

foobuzz commented Jul 7, 2020

@nleroy917 Even using a browser driver would still need to have either the username/password or an authentication cookie of a given user to load the authorization screen for this user. If one is willing to transfer such secrets to a remote machine beforehand, why not pass the authorization step on a local machine beforehand, and then transfer the OAuth token to the remote machine.

@nleroy917
Copy link
Contributor

You make a good point. But, I think it's more flexible (and scales better) to just type my username and password as environment variables and have Selenium do the work for me, rather than run a script locally and copy and paste a key into my PaaS dashboard every time I lose my authorization and refresh token

@stephanebruckert
Copy link
Member

The point of using OAuth is that there is no middleman between the user and Spotify. Since the user only gives the password to spotify.com, even the library (in this case spotipy) cannot see the password.

It's definitely possible to hack something that allows direct password login, however it's highly disrecommended:

  • the password is eventually typed or stored outside of the user's browser -> it could potentially be found in the terminal history or in some files, whereas a browser doesn't leave such trace
  • the library and its whole tree of dependencies must be trusted, which can be a lot to ask when there is already a way to do it securely

As a user I think being redirected to spotify.com in a browser at least once is fine, particularly if the user is already signed in:

  • if using a spotipy webapp, the user can just click a link and sign in
  • if using a spotipy script, it's probably fine to get redirected to a browser only once in order to sign in

However I understand the frustration as a developer who is trying to run a script remotely and continuously. I've overcome this issue by caching the spotify token into DynamoDB (code here). Basically:

  • run the script locally at least once,
  • if DB has no token redirect to browser and persist the token in the DB,
  • deploy and run the script wherever,
  • there should be a token in the DB so there won't be any prompt, then refresh the old token and persist

Hopefully we can implement this into spotipy one day as part of #51. Someone also suggested Redis!

Copy link
Member

@stephanebruckert stephanebruckert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one thing to remove

Comment on lines +378 to +386
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)
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sure, I just realised this not only adds an option to force pasting the resulting URL, but it also prints the initial URL 👍

@@ -421,7 +433,7 @@ def get_auth_response(self):

logger.info('Paste that url you were directed to in order to '
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we should remove this line, as we already print "Enter the URL you were redirected to: " in _get_auth_response_interactive

@stephanebruckert
Copy link
Member

Thanks a lot for this @foobuzz, merging it now will close 3 issues at once!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants