I am having a great deal of trouble trying to authorize Spotipy. It does fine in IDLE, but as soon I try to do it together with Django, hell lets loose. Where I have gotten the farthest, is where I have gotten the code from the callback. But what do I do after that?
This piece of code below takes
def add_song(request, pk, uri):
scope = "user-read-recently-played user-top-read user-read-playback-position user-read-playback-state user-modify-playback-state user-read-currently-playing user-read-private playlist-modify-private playlist-read-private"
token = util.prompt_for_user_token("username", scope, client_id=SECRET,client_secret=SECRET,redirect_uri='http://localhost:8000/rooms/callback/')
sp = spotipy.Spotify(auth=token)
sp.add_to_queue(uri)
auth_manager = spotipy.oauth2.SpotifyOAuth(scope='user-read-currently-playing playlist-modify-private', show_dialog=True)
if request.GET.get("code"):
# Step 3. Being redirected from Spotify auth page
auth_manager.get_access_token(request.GET.get("code"))
return redirect('/')
return render(request, 'rooms/{0}.html'.format(pk))
def callback(request):
return redirect(reverse("index"))
The URL patterns:
urlpatterns = [
path('<int:pk>/add_song/<str:uri>', views.add_song, name='add_song'),
path('callback/<str:token>', views.callback, name="callback")
]
I really need something to continue on. I have been googling so much, but there isn't really anything I know what to do with. Could you provide an example of Django Python authorization or try to help me?
Don't use util.prompt_for_user_token. Assuming you want different users to use your app, how would you pre-know their username?
Use a SpotifyOAuth object instead (you were using it, just not in the way I'm about to show). You can refer to the Client Authorization Code Flow section on the official docs.
Here is the basic of it:
# Seprating the login from add_song()
def login(request):
sp_auth = SpotifyOAuth(
scope=scope,
client_id=client_id,
client_secret=client_secret,
redirect_uri=redirect_uri,
)
# This url will prompt you a Spotify login page, then redirect user to your /callback upon authorization
redirect_url = sp_auth.get_authorize_url() # Note: You should parse this somehow. It may not be in a pretty format.
return redirect(redirect_url)
# You can make add_song() and callback() the same function.
# If you have multiple pages where you need to use the API, I suggest caching the token here as well.
def callback_and_add_song():
sp_auth = [the same as above. You should make a method to modularize it.]
# Get code from url
code = request.GET.get("code", "")
token = sp_auth.get_access_token(code=code)
<You have token now. Do what you need.>
A few more things I noticed in your code that may be not-needed/ wrong:
path('callback/<str:token>': I don't think <str:token> have to be there.
return redirect('/'): In the first if in add_song(), assuming your previous code works, you are still just redirecting somewhere without caching the token/ passing it on.
return redirect(reverse("index")): The same as above. I think for performance and all, you should cache token in callback() then redirect to page(s) where you actually use it.
Related
I am working on code to login to my app using Microsoft login using fastapi. I am able to call microsoft login page and after entering the login credentials i am getting the error "{"detail":"'state' parameter in callback request does not match our internal 'state', someone may be trying to do something bad."}"
**main.py**
from fastapi_sso.sso.microsoft import MicrosoftSSO
app1 = FastAPI()
microsoft_sso = MicrosoftSSO("client-id", "client-secret", "http://localhost:8000/dashboard")
#app1.get("/microsoft/login")
async def microsoft_login():
return await microsoft_sso.get_login_redirect()
#app1.get("/dashboard")
async def microsoft_callback(request: Request):
user_new = await microsoft_sso.verify_and_process(request) #getting error at this point
print(user_new.id)
print("Hello Microsoftttttttttttttttt")
I had the same problem with this SOO using the google one. The solution was to create a new pair of keys and use one when developing the platform locally and the other one being used only in production. After doing this, the issue faded away. Just like that.
You probably need to pass use_state=False in the SSO constructor:
microsoft_sso = MicrosoftSSO("client-id", "client-secret", "http://localhost:8000/dashboard", use_state=False)
If the application is distributed (i.e multiple web servers), or if it plans to handle multiple simultaneous SSO flows (i.e login request for user A, then login request for user B, then callback for A then callback for B), then the default use_state=True is a bad idea. No idea how much more secure is it, but the above reasons are enough for me to disable this "feature".
What it does is, when generating the redirect url it generates a state, stores it locally and passes it as a cookie to provider, then in callback it checks if the cookie matches local state.
I'm working on an app using the Spotify API but I'm a bit new to all of this. I'm trying to get the Authorization Code with Proof Key for Code Exchange (PKCE) (https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce)
My problem is how do I redirect the user to the query where he has to ACCEPT the authorization and make my app to wait until the user clicks on ACCEPT. When he does this, the user will be redirected and that new URL (as the docs said) will contain the authorization code that I need to then exchange it for an authorization token.
This is my function so far to get that authorization code:
def get_auth_code(self):
code_challenge = self.get_code_challenge_PKCE()
scopes_needed = "user-read-email%20user-read-private%20playlist-read-collaborative%20playlist-modify-public%20playlist-read-private%20playlist-modify-private%20user-library-modify%20user-library-read"
endpoint = "https://accounts.spotify.com/authorize"
query = f"{endpoint}?client_id={self.client_ID}&response_type=code&redirect_uri={self.redirect_uri}&scope={scopes_needed}&code_challenge_method=S256&code_challenge={code_challenge}"
webbrowser.open(query)
Set up a web server.
To programmatially extract the access tokens you need a web server to handle the redirection after the user logs in on Spotify (which you redirected them to). Now this server can be the user pasting the URI to an input field on a terminal, but obviously this isn't ideal for user experience. It leaves room for lots of mistakes.
I've authored a Spotify Web API client, whose internals might be useful for you to examine. For example, you can use Flask to construct the server. The main principle is using one endpoint (i.e. /login) to redirect (code 307 worked for me browsers won't remember it) the user to a callback (i.e. /callback) which recieves the code parameter with which you can request an access token.
OAuth2 can be a bit of a pain to implement locally, I know. In my library I also made a similar function that you are constructing using webbrowser, but it does have the manual copy-pasting quirk. To use functions you can define yourself for brevity, the gist of it is:
verifier = secrets.token_urlsafe(32) # for PKCE, not in my library yet
url = user_authorisation_url(scope, state, verifier)
# Communicate with the user
print('Opening browser for Spotify login...')
webbrowser.open(url)
redirected = input('Please paste redirect URL: ').strip()
code = parse_code_from_url(redirected)
state_back = parse_state_from_url(redirected)
assert state == state_back # For that added security juice
token = request_user_token(code, verifier)
Flask-Login recommends having an is_safe_url() function after user login:
Here is a link to the part of the documentation that discusses this:
https://flask-login.readthedocs.io/en/latest/#login-example
They link to this snippet but I don't understand how it implements is_safe_url():
https://palletsprojects.com/p/flask/
next = request.args.get('next')
if not is_safe_url(next):
return abort(400)
This doesn't seem to come with Flask. I'm relatively new to coding. I want to understand:
What exactly is happening when request gets the next argument?
What does the is_safe_url() function do to ensure the URL is safe?
Does the next URL need to be checked on login only? Or are there other places and times when it is important to include this security measure?
And most importantly: is there a reliable is_safe_url() function that I can use with Flask?
Edit: Added link to Flask-Login documentation and included snippet.
As mentioned in the comments, Flask-Login today had a dead link in the documentation (issue on GitHub). Please note the warning in the original flask snippets documentation:
Snippets are unofficial and unmaintained. No Flask maintainer has curated or checked the snippets for security, correctness, or design.
The snippet is
from urllib.parse import urlparse, urljoin
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and \
ref_url.netloc == test_url.netloc
Now to address your questions:
What exactly is happening when request gets the next argument?
Part of the code we are focusing on here is
next = request.args.get('next')
return redirect(next or url_for('dashboard'))
which redirects user to dashboard (e.g. after successful login) by default. However, if user tried to reach for e.g. endpoint profile and wasn't logged in we would want to redirect him to the login page. After logging in default redirect would redirect user to dashboard and not to profile where he intended to go. To provide better user experience we can redirect user to his profile page by building URL /login?next=profile, which enables flask to redirect to profile instead of the default dashboard.
Since user can abuse URLs we want to check if URL is safe, or abort otherwise.
What does the is_safe_url() function do to ensure the URL is safe?
The snippet in question is a function that ensures that a redirect target will lead to the same server.
Does the next URL need to be checked on login only? Or are there other places and times when it is important to include this security measure?
No, you should check all dangerous URLs. Example of safe URL redirect would be redirect(url_for('index')), since its hardcoded in your program. See examples of safe and dangerous URLs on Unvalidated Redirects and Forwards - OWASP cheatsheet.
And most importantly: is there a reliable is_safe_url() function that I can use with Flask?
There is Django's is_safe_url() bundled as a standalone package on pypi.
The accepted answer covers the theory well. Here is one way to deploy a 'safe URL' strategy throughout your Flask project, involving no additional libraries:
util.py:
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
def get_redirect_target():
for target in request.values.get('next'), request.args.get('next'):
if not target:
continue
if is_safe_url(target):
return target
def redirect_back(endpoint, **values):
target = request.form['next'] if request.form and 'next' in request.form else request.args.get('next')
if not target or not is_safe_url(target):
target = url_for(endpoint, **values)
return redirect(target)
example routes.py (one of many):
from util import get_redirect_target, redirect_back
#bp.route('/routepath', methods=['GET', 'POST'], strict_slashes=False)
#login_required
def my_route():
# use these styles (both or separately) as needed
if not (some_condition):
return redirect_back('someother_route')
return_url = get_redirect_target() or url_for('another_route')
...
return redirect(return_url)
is_safe_url is a python module.
Your code is ok just import after you install the module:
#install:
python3 -m pip install is_safe_url
#import:
from is_safe_url import is_safe_url
I have made a simple web form in Google app engine where I have added a recaptcha component.
The component is showing up on my web page. But i have no idea how to make the api call.
my code is;
def post(self):
challenge = self.request.get('recaptcha_challenge_field')
response = self.request.get('recaptcha_response_field')
remoteip = os.environ['REMOTE_ADDR']
private_key = 'xxx'
cResponse = self.request.submit(http://www.google.com/recaptcha/api/verify?privatekey="private_key"&remoteip="remoteip"&challenge="challenge"&response="response")
if cResponse.is_valid:
# response was valid
# other stuff goes here
pass
else:
error = cResponse.error_code
its pretty clear that my api call is completely wrong but i have no idea how to make it.
The examples i have seen use the plugin.
Use the URL Fetch API documented here, the first example in the linked page should be suitable for your needs.
Notice that url fetches have a quota and are billable.
this is my first web-programming experience so I hope my questions doesn't sound very dumb. I have been stucked on this for many days.
I am trying to understand a sample code:
https://github.com/facebook/runwithfriends
However I am not understanding very well how the information flow works and how can I modify that sample (i.e. how the code works).
For example, in the following section of the code:
class RecentRunsHandler(BaseHandler):
"""Show recent runs for the user and friends"""
def get(self):
if self.user:
friends = {}
for friend in select_random(
User.get_by_key_name(self.user.friends), 30):
friends[friend.user_id] = friend
self.render(u'runs',
friends=friends,
user_recent_runs=Run.find_by_user_ids(
[self.user.user_id], limit=5),
friends_runs=Run.find_by_user_ids(friends.keys()),
)
else:
self.render(u'welcome')
As I understand (along with HTML) is useful for showing friends that are using the same app, and if I understand correctly, here is the essential part:
*friends_runs=Run.find_by_user_ids(friends.keys())*
But what if I want to show any given friend. How can I do it?
Summarizing, I would like to know:
1- How the flow of the code works? (I don't fully understand the explanation here)
2- How can I manipulate the code so to get, for example, to show a list of friends of the user (not necessary that use the same app)?
Moreover, Can I show friends filtered by some characteristic (for example, gender)?
Thanks a lot!
The python "SDK" for facebook I use I took from https://gist.github.com/1190267
and combined it with the code from the example app to achieve the functionality I wanted both for a canvas app and for website usage.
It depends whether you're using facebook with websites or a canvas application. For a canvas application you probably could do well with the javascript SDK but for a "login with facebook" I required serverside logic that should work with javascript turned off so I've completed that solution with details you might have help to know. You can try make small changes of that specific app 'runwithfriends' to get an understanding which code does what. The project you're looking at contains some outdated practice though:
getting and setting cookies is likely preferable now doing with webapp2's builtin functions for this instead of the code that comes with the FB example app
logging in and out is now done with OAuth 2.0 so it's likely that the login system you're looking at is outdated and you need to use OAuth 2.0 which is described here. I much rather do login/logout serverside so I did an OAuth 2.0 pure python solution to login / logout following the authentication steps mentioned in the tutorial from FB. I had to clear the cookie to log a user out which was not documented.
To upgrade to python 2.7 I had to also modify so that HTTP header did not cast to unicode. I don't know why but otherwise it complained that headers were "not strings"
To more elaborately answer your specific questions:
1) The requesthandler class you posted is a subclass of a BaseHandler so to fully understand what it does you can look at the BaseHandler class since what you are posting is a BAseHandler. The BaseHandler uses django templates for rendering and if you want to can switch the template engine to jinja2 which is remmended. Further the code accesses the user object inherited from the BaseHandler and does some operations on it and renders it to a template. You can try make a requesthandler of your own, subclass BaseHandler and do what you want.
2) I could manipulate the code and I'm not an expert so you should be able to do it too. I wanted a simple FB app to display random images and I could manipulate it to select random images via blobs and render to to a template while keeping the facebook base functions. A function to use for getting the user using the Graph API I do this:
def parse_signed_request(signed_request, secret):
"""
Parse signed_request given by Facebook (usually via POST),
decrypt with app secret.
Arguments:
signed_request -- Facebook's signed request given through POST
secret -- Application's app_secret required to decrpyt signed_request
"""
if '.' in signed_request:
(esig, payload) = signed_request.split('.')
else:
return {}
sig = urlsafe_b64decode(str(esig))
data = _parse_json(urlsafe_b64decode(str(payload)))
if not isinstance(data, dict):
raise SignedRequestError('Pyload is not a json string!')
return {}
if data['algorithm'].upper() == 'HMAC-SHA256':
if hmac.new(secret, payload, hashlib.sha256).digest() == sig:
return data
else:
raise SignedRequestError('Not HMAC-SHA256 encrypted!')
return {}
def get_user_from_cookie(cookies, app_id, app_secret):
"""Parses the cookie set by the official Facebook JavaScript SDK.
cookies should be a dictionary-like object mapping cookie names to
cookie values.
If the user is logged in via Facebook, we return a dictionary with the
keys "uid" and "access_token". The former is the user's Facebook ID,
and the latter can be used to make authenticated requests to the Graph API.
If the user is not logged in, we return None.
Download the official Facebook JavaScript SDK at
http://github.com/facebook/connect-js/. Read more about Facebook
authentication at http://developers.facebook.com/docs/authentication/.
"""
cookie = cookies.get('fbsr_' + app_id, '')
if not cookie:
return None
response = parse_signed_request(cookie, app_secret)
if not response:
return None
args = dict(code=response['code'], client_id=app_id,
client_secret=app_secret, redirect_uri='')
file = \
urllib.urlopen('https://graph.facebook.com/oauth/access_token?'
+ urllib.urlencode(args))
try:
token_response = file.read()
finally:
file.close()
access_token = cgi.parse_qs(token_response)['access_token'][-1]
logging.debug('returning cookie')
return dict(uid=response['user_id'], access_token=access_token)
See http://developers.facebook.com/docs/api for complete documentation for the API. And you can get the the official Facebook JavaScript SDK at http://github.com/facebook/connect-js/
I'm now writing code to sync a webapp2_extras.auth account with facebook so that custom accounts and facebook accounts can co-exist and we're discussing solutions for this in the webapp2 groups and categories. The current way I do it is adding the recommended current_user to a basehandler and using that as the FB identity while working on "merging" my class FBUser that is a custom class for facebook users that autheorized my website and/or canvas application to sync with webapp2_extras.auth.models.User which is an expando model so it can just add the properties it doesn't have such as facebookid, firstname, lastname, etc.
#property
def current_user(self):
if not hasattr(self, '_current_user'):
self._current_user = None
cookie = get_user_from_cookie(self.request.cookies,
facebookconf.FACEBOOK_APP_ID,
facebookconf.FACEBOOK_APP_SECRET)
if cookie:
# Store a local instance of the user data so we don't need
# a round-trip to Facebook on every request
user = FBUser.get_by_key_name(cookie['uid'])
if not user:
graph = GraphAPI(cookie['access_token'])
profile = graph.get_object('me')
user = FBUser(key_name=str(profile['id']),
id=str(profile['id']),
name=profile['name'],
profile_url=profile['link'],
access_token=cookie['access_token'])
user.put()
elif user.access_token != cookie['access_token']:
user.access_token = cookie['access_token']
user.put()
self._current_user = user
return self._current_user
You can also solve your authentication with session objects and build your authentication system around that. That is what I do when using both custom accounts and facebook accounts and you're welcome to have a lok at my repository for more code examples how to intregrate facebook with google app engine using python 2.7.