So I'm trying to build an authorization flow with with the Spotify Web API.
So the first call is just a GET against a URL and I'm passing the credentials and other stuff necessary to authorize my app as parameters.
After that the user is prompted to authorize my app then is redirected to a callback URL.
The url I'm redirected to contains some response data as parameters and I need that data to then POST against the API and retrieve the token.
How do I retrieve the response URL and access those parameters? Sorry for the naive question. Thanks in advance.
I don't believe you can do this with just requests.
I would check out a package like: https://flask-oauthlib.readthedocs.org/en/latest/
from flask import Flask, redirect, url_for, session, request
from flask_oauthlib.client import OAuth, OAuthException
SPOTIFY_APP_ID = 'REGULAR_CODE'
SPOTIFY_APP_SECRET = 'SECRET_CODE'
app = Flask(__name__)
app.debug = True
app.secret_key = 'development'
oauth = OAuth(app)
spotify = oauth.remote_app(
'spotify',
consumer_key=SPOTIFY_APP_ID,
consumer_secret=SPOTIFY_APP_SECRET,
# Change the scope to match whatever it us you need
# list of scopes can be found in the url below
# https://developer.spotify.com/web-api/using-scopes/
request_token_params={'scope': 'user-read-email'},
base_url='https://accounts.spotify.com',
request_token_url=None,
access_token_url='/api/token',
authorize_url='https://accounts.spotify.com/authorize'
)
#app.route('/')
def index():
return redirect(url_for('login'))
#app.route('/login')
def login():
callback = url_for(
'spotify_authorized',
next=request.args.get('next') or request.referrer or None,
_external=True
)
return spotify.authorize(callback=callback)
#app.route('/login/authorized')
def spotify_authorized():
resp = spotify.authorized_response()
if resp is None:
return 'Access denied: reason={0} error={1}'.format(
request.args['error_reason'],
request.args['error_description']
)
if isinstance(resp, OAuthException):
return 'Access denied: {0}'.format(resp.message)
session['oauth_token'] = (resp['access_token'], '')
me = spotify.get('/me')
return 'Logged in as id={0} name={1} redirect={2}'.format(
me.data['id'],
me.data['name'],
request.args.get('next')
)
#spotify.tokengetter
def get_spotify_oauth_token():
return session.get('oauth_token')
if __name__ == '__main__':
app.run()
You can GET a JSON response with all parameters that you need, example.
You login to a www.example.com/login/
GET http://www.example.com/login/ (With header and Basic access authentication or the auth process that you use) and this URL return a JSON Response with the data:
{
'status': 'OK',
'token': {
'public_token': 'blabla',
'private_token': 'blabla'},
'created_at': '2014-11-03'
}
That data you can save in database for future use.
Related
Using the example for testing: https://github.com/spotipy-dev/spotipy/blob/master/examples/app.py
Trying to use a React frontend that gets the html from the backend ("http://127.0.0.1:8080", which is my callback).
When I try to request the data for it, I keep getting errors and undefined states. However when I just simply go to the callback redirect url and get data, it works perfectly What could be the issue? I set the necessary env variables
Here is my python and React code
React code:
Python:
import os
from flask import Flask, session, request, redirect
from flask_cors import CORS, cross_origin
from flask_session import Session
import spotipy
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(64)
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_FILE_DIR'] = './.flask_session/'
CORS(app)
Session(app)
client_id = os.getenv("SPOTIPY_CLIENT_ID")
client_secret = os.getenv("SPOTIPY_CLIENT_SECRET")
redirect_uri = os.getenv("SPOTIPY_REDIRECT_URI")
#app.route('/')
def index():
cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
auth_manager = spotipy.oauth2.SpotifyOAuth(scope='user-read-currently-playing playlist-modify-private',
cache_handler=cache_handler,
show_dialog=True)
if request.args.get("code"):
# Step 2. Being redirected from Spotify auth page
auth_manager.get_access_token(request.args.get("code"))
return redirect('/')
if not auth_manager.validate_token(cache_handler.get_cached_token()):
# Step 1. Display sign in link when no token
auth_url = auth_manager.get_authorize_url()
return f'<h2>Sign in</h2>'
# Step 3. Signed in, display data
spotify = spotipy.Spotify(auth_manager=auth_manager)
return f'<small><a href="/sign_out">[sign out]<a/></small></h2>' \
f'my playlists | ' \
f'currently playing | ' \
f'me' \
#app.route('/sign_out')
def sign_out():
session.pop("token_info", None)
return redirect('/')
#app.route('/playlists')
def playlists():
cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
auth_manager = spotipy.oauth2.SpotifyOAuth(cache_handler=cache_handler)
if not auth_manager.validate_token(cache_handler.get_cached_token()):
return redirect('/')
spotify = spotipy.Spotify(auth_manager=auth_manager)
return spotify.current_user_playlists()
#app.route('/currently_playing')
def currently_playing():
cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
auth_manager = spotipy.oauth2.SpotifyOAuth(cache_handler=cache_handler)
if not auth_manager.validate_token(cache_handler.get_cached_token()):
return redirect('/')
spotify = spotipy.Spotify(auth_manager=auth_manager)
track = spotify.current_user_playing_track()
if not track is None:
return track
return "No track currently playing."
#app.route('/current_user')
def current_user():
cache_handler = spotipy.cache_handler.FlaskSessionCacheHandler(session)
auth_manager = spotipy.oauth2.SpotifyOAuth(cache_handler=cache_handler)
if not auth_manager.validate_token(cache_handler.get_cached_token()):
return redirect('/')
spotify = spotipy.Spotify(auth_manager=auth_manager)
return spotify.current_user()
'''
Following lines allow application to be run more conveniently with
`python app.py` (Make sure you're using python3)
(Also includes directive to leverage pythons threading capacity.)
'''
if __name__ == '__main__':
app.run(threaded=True, port=int(os.environ.get("PORT",
os.environ.get("SPOTIPY_REDIRECT_URI", "8080").split(":")[-1])))
I have this callback url that has a redirection:
#spotify_auth_bp.route("/callback", methods=['GET', 'POST'])
def spotify_callback():
SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
CLIENT_ID = os.environ.get('SPOTIPY_CLIENT_ID')
CLIENT_SECRET = os.environ.get('SPOTIPY_CLIENT_SECRET')
REDIRECT_URI = os.environ.get('SPOTIPY_REDIRECT_URI')
auth_token = request.args['code']
code_payload = {
"grant_type": "authorization_code",
"code": auth_token,
"redirect_uri": REDIRECT_URI,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
}
post_request = requests.post(SPOTIFY_TOKEN_URL, data=code_payload)
# Auth Step 5: Tokens are Returned to Application
response_data = json.loads(post_request.text)
access_token = response_data["access_token"]
refresh_token = response_data["refresh_token"]
token_type = response_data["token_type"]
expires_in = response_data["expires_in"]
token_info = {'access_token': access_token,
'refresh_token': refresh_token,
'token_type': token_type,
'expires_in': expires_in}
res = make_response(redirect('http://localhost/about', code=302))
# I'd rather send token_info to `localStorage` at my client, instead of setting cookies
#res.set_cookie('access_token', access_token)
#res.set_cookie('refresh_token', refresh_token)
#res.set_cookie('token_type', token_type)
#res.set_cookie('expires_in', str(expires_in))
return res
Is there a way of sending 'token_info' above with jsonify() (or else) to client via make_response() before redirection occurs?
Please read foot-notes if your concern is about the OAuth flow!
According to HTTP 302 Redirect - is a message-body needed?, you can return a body in a 302 Redirect. Indeed RFC2616 doesn't forbid to send a response body.
Well, I'm not a Flask expert, but this should do the job. It returns the JSON in the body of the 302 Redirect response. Pay attention that you should also set the right Content-Type header (not done in my example).
import json
from flask import Flask, redirect, Response
app = Flask(__name__)
def jsonResponseFactory(data):
'''Return a callable in top of Response'''
def callable(response=None, *args, **kwargs):
'''Return a response with JSON data from factory context'''
return Response(json.dumps(data), *args, **kwargs)
return callable
#app.route('/')
def hello_world():
token_info = {
'access_token': '...',
'refresh_token': '...',
'token_type': '...',
'expires_in': '...'
}
return redirect(
'http://localhost/about',
302,
jsonResponseFactory(token_info)
)
Btw, if your client needs to read the token, it should not automatically follow the redirect! Furthermore, I don't know if a 302 Redirect is the best answer to your OAuth callback endpoint. But all of this depends on the context.
Edit:
Here are some notes after feedback from comments.
You should not store tokens in the localStorage. There is a good explanation from Auth0 website. Furthermore, browsers (starting with Safari) will now drop localStorage after 7 days without user interaction
Again from Auth0 (and I fully agree) the token should be handled server-side.
I'll let my answer as it is (even if the scope is not ideal) because it's actually answer to your HTTP related question.
Right now I can obtain an access token using requests_oauthlib and a scope. However I'd like to be able to get the full ID_Token and was wondering if it was possible with the way I'm doing things.
import flask
import requests_oauthlib
import os
import requests
CLIENT_ID = "ClientIDKEY"
CLIENT_SECRET = "CLIENTSECRETKEY"
redirect_uri = "http://localhost:5000/callback"
AUTHORIZATION_BASE_URL = "https://accounts.google.com/o/oauth2/auth"
TOKEN_URL = "https://oauth2.googleapis.com/token"
USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"
SCOPE_URL = "https://www.googleapis.com/auth/userinfo.profile"
# This allows us to use a plain HTTP callback
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
app = flask.Flask(__name__)
#app.route("/")
def index():
return """
Login with Google
"""
#app.route("/login")
def login():
simplelogin = requests_oauthlib.OAuth2Session(
CLIENT_ID, redirect_uri=redirect_uri, scope=SCOPE_URL
)
authorization_url, _ = simplelogin.authorization_url(AUTHORIZATION_BASE_URL)
return flask.redirect(authorization_url)
#app.route("/callback")
def callback():
simplelogin = requests_oauthlib.OAuth2Session(CLIENT_ID, redirect_uri=redirect_uri)
simplelogin.fetch_token(
TOKEN_URL, client_secret=CLIENT_SECRET, authorization_response=flask.request.url
)
URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=" + str(simplelogin.access_token)
req = requests.get(url = URL)
print(req.json)
return f"""
Ok
"""
if __name__ == "__main__":
app.run(host="localhost", debug=True)
I'd like to either obtain the ID token when authenticating instead of the Access Token, or simply use the Access Token from the authentication to obtain the ID_Token.
The final result here, not in the scope of this question, is to use the jwt token and validate it with cloud endpoints, so they can be used on a REST api on the backend.
So I managed to do it with python 2.7 (since for some reason they just decided to use 2.7) but the concept is the same.
In the SCOPE_URL I passed ["openid"], which made the request return and ID_Token. I then used that ID_Token and made a call such as:
AUTHORIZATION_BASE_URL = "https://accounts.google.com/o/oauth2/auth"
TOKEN_URL = "https://oauth2.googleapis.com/token"
USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"
SCOPE_URL = ["openid"]
(...)
#app.route("/callback")
def callback():
simplelogin = requests_oauthlib.OAuth2Session(CLIENT_ID, redirect_uri=redirect_uri)
simplelogin.fetch_token(
TOKEN_URL, client_secret=CLIENT_SECRET, authorization_response=flask.request.url
)
ID_Token = simplelogin.token.get('id_token')
URL = "https://oauth2.googleapis.com/tokeninfo?id_token=" + str(ID_Token)
req = requests.get(url=URL)
print(req.content)
return """
Ok
"""
I may have a bad understanding of how the flask session works, but I am trying to generate a Spotify API access token using SpotiPY with the Authorization Code Flow, and store it in Flask's session storage.
The program doesn't seem to be able to store it, and therefore later runs in to an error when trying to call it. Here is a visual explanation with images and captions: https://imgur.com/a/KiYZFiQ
Here is the main server script:
from flask import Flask, render_template, redirect, request, session, make_response,session,redirect
import spotipy
import spotipy.util as util
from credentz import *
app = Flask(__name__)
app.secret_key = SSK
#app.route("/")
def verify():
session.clear()
session['toke'] = util.prompt_for_user_token("", scope='playlist-modify-private,playlist-modify-public,user-top-read', client_id=CLI_ID, client_secret=CLI_SEC, redirect_uri="http://127.0.0.1:5000/index")
return redirect("/index")
#app.route("/index")
def index():
return render_template("index.html")
#app.route("/go", methods=['POST'])
def go():
data=request.form
sp = spotipy.Spotify(auth=session['toke'])
response = sp.current_user_top_artists(limit=data['num_tracks'], time_range=data['time_range'])
return render_template("results.html",data=data)
if __name__ == "__main__":
app.run(debug=True)
and here are the two html files: Index.html, Results.html
Some things worth noting:
Credentz stores all of the private info including SSK, CLI_SEC, and CLI_ID.
The spotipy request works fine if I do it in a non-flask environment with no web-browser interaction.
I am able to store other things in the session storage, and call it back later, just not the access token for some reason.
My best guess is that the page doesn't have time to store it before the Spotify api redirects the page, not sure though.
Any help is really appreciated, Thank You!
You are right, spotify is redirecting you before the token can be added therefore ending the execution of that function. So you need to add a callback step to grab the authentication token after you've been redirected.
Spotitpy's util.prompt_for_user_token method is not designed to be used in a web server so your best best is to implement the auth flow your self and then use spotipy once you have the token. Luckily the spotify authorization flow is pretty simple and easy to implement.
Method 1: Implementing auth flow our self:
from flask import Flask, render_template, redirect, request, session, make_response,session,redirect
import spotipy
import spotipy.util as util
from credentz import *
import requests
app = Flask(__name__)
app.secret_key = SSK
API_BASE = 'https://accounts.spotify.com'
# Make sure you add this to Redirect URIs in the setting of the application dashboard
REDIRECT_URI = "http://127.0.0.1:5000/api_callback"
SCOPE = 'playlist-modify-private,playlist-modify-public,user-top-read'
# Set this to True for testing but you probably want it set to False in production.
SHOW_DIALOG = True
# authorization-code-flow Step 1. Have your application request authorization;
# the user logs in and authorizes access
#app.route("/")
def verify():
auth_url = f'{API_BASE}/authorize?client_id={CLI_ID}&response_type=code&redirect_uri={REDIRECT_URI}&scope={SCOPE}&show_dialog={SHOW_DIALOG}'
print(auth_url)
return redirect(auth_url)
#app.route("/index")
def index():
return render_template("index.html")
# authorization-code-flow Step 2.
# Have your application request refresh and access tokens;
# Spotify returns access and refresh tokens
#app.route("/api_callback")
def api_callback():
session.clear()
code = request.args.get('code')
auth_token_url = f"{API_BASE}/api/token"
res = requests.post(auth_token_url, data={
"grant_type":"authorization_code",
"code":code,
"redirect_uri":"http://127.0.0.1:5000/api_callback",
"client_id":CLI_ID,
"client_secret":CLI_SEC
})
res_body = res.json()
print(res.json())
session["toke"] = res_body.get("access_token")
return redirect("index")
# authorization-code-flow Step 3.
# Use the access token to access the Spotify Web API;
# Spotify returns requested data
#app.route("/go", methods=['POST'])
def go():
data = request.form
sp = spotipy.Spotify(auth=session['toke'])
response = sp.current_user_top_artists(limit=data['num_tracks'], time_range=data['time_range'])
return render_template("results.html", data=data)
if __name__ == "__main__":
app.run(debug=True)
On the other hand we can cheat a little bit and use the methods that spotipy itself is using in order to save us some code.
Method 2: Implementing auth flow (along with custom token management) using spotipy.oauth2.SpotifyOAuth method:
from flask import Flask, render_template, redirect, request, session, make_response,session,redirect
import spotipy
import spotipy.util as util
from credentz import *
import time
import json
app = Flask(__name__)
app.secret_key = SSK
API_BASE = 'https://accounts.spotify.com'
# Make sure you add this to Redirect URIs in the setting of the application dashboard
REDIRECT_URI = "http://127.0.0.1:5000/api_callback"
SCOPE = 'playlist-modify-private,playlist-modify-public,user-top-read'
# Set this to True for testing but you probaly want it set to False in production.
SHOW_DIALOG = True
# authorization-code-flow Step 1. Have your application request authorization;
# the user logs in and authorizes access
#app.route("/")
def verify():
# Don't reuse a SpotifyOAuth object because they store token info and you could leak user tokens if you reuse a SpotifyOAuth object
sp_oauth = spotipy.oauth2.SpotifyOAuth(client_id = CLI_ID, client_secret = CLI_SEC, redirect_uri = REDIRECT_URI, scope = SCOPE)
auth_url = sp_oauth.get_authorize_url()
print(auth_url)
return redirect(auth_url)
#app.route("/index")
def index():
return render_template("index.html")
# authorization-code-flow Step 2.
# Have your application request refresh and access tokens;
# Spotify returns access and refresh tokens
#app.route("/api_callback")
def api_callback():
# Don't reuse a SpotifyOAuth object because they store token info and you could leak user tokens if you reuse a SpotifyOAuth object
sp_oauth = spotipy.oauth2.SpotifyOAuth(client_id = CLI_ID, client_secret = CLI_SEC, redirect_uri = REDIRECT_URI, scope = SCOPE)
session.clear()
code = request.args.get('code')
token_info = sp_oauth.get_access_token(code)
# Saving the access token along with all other token related info
session["token_info"] = token_info
return redirect("index")
# authorization-code-flow Step 3.
# Use the access token to access the Spotify Web API;
# Spotify returns requested data
#app.route("/go", methods=['POST'])
def go():
session['token_info'], authorized = get_token(session)
session.modified = True
if not authorized:
return redirect('/')
data = request.form
sp = spotipy.Spotify(auth=session.get('token_info').get('access_token'))
response = sp.current_user_top_tracks(limit=data['num_tracks'], time_range=data['time_range'])
# print(json.dumps(response))
return render_template("results.html", data=data)
# Checks to see if token is valid and gets a new token if not
def get_token(session):
token_valid = False
token_info = session.get("token_info", {})
# Checking if the session already has a token stored
if not (session.get('token_info', False)):
token_valid = False
return token_info, token_valid
# Checking if token has expired
now = int(time.time())
is_token_expired = session.get('token_info').get('expires_at') - now < 60
# Refreshing token if it has expired
if (is_token_expired):
# Don't reuse a SpotifyOAuth object because they store token info and you could leak user tokens if you reuse a SpotifyOAuth object
sp_oauth = spotipy.oauth2.SpotifyOAuth(client_id = CLI_ID, client_secret = CLI_SEC, redirect_uri = REDIRECT_URI, scope = SCOPE)
token_info = sp_oauth.refresh_access_token(session.get('token_info').get('refresh_token'))
token_valid = True
return token_info, token_valid
if __name__ == "__main__":
app.run(debug=True)
Either method is equally valid in my opinion, just go with whichever method you find easier to understand.
Make sure that you update the authorized Redirect URIs in your spotify application dashboard.
See the spotify docs for more info: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow
I'm trying to develop a web app using Google App Engine.
The language i'm using is Python and i'm using the facebook-sdk (https://facebook-sdk.readthedocs.io/en/latest/).
I'm stuck at making the OAuth Facebook login works correctly.
Every single time I attempt to log in to my app using Facebook login i get the error
URL Blocked: This redirect failed because the redirect URI is not whitelisted in the app’s Client OAuth Settings. Make sure Client and Web OAuth Login are on and add all your app domains as Valid OAuth Redirect URIs.
You can see the screenshot of the error (in the original language) below
That are the settings of my Facebook app
That's the code of my app's RequestHandler which handle the login's requests
import facebook
import os
import webapp2
import urllib
from webapp2_extras import sessions
facebook_ID = 'xxxxxxxxxxx'
facebook_secret = 'xxxxxxxxxxxxxxxxxxxxxx'
facebook_graphUrl = 'https://graph.facebook.com/v2.8'
facebook_redirectUri = 'http://gold-circlet-160109.appspot.com/login/'
facebook_requestCode = 'https://www.facebook.com/v2.8/dialog/oauth?' +
urllib.urlencode(
{'client_id':facebook_ID,
'redirect_uri':facebook_redirectUri,
'scope':'public_profile, email, user_friends'})
def retrieve_access_token(code):
args = {'redirect_uri': facebook_redirectUri,
'client_id': facebook_ID,
'client_secret': facebook_secret,
'code': code}
access_token = urllib.urlopen(facebook_graphUrl + "/oauth/access_token?" + urllib.urlencode(args)).read()
access_token = urlparse.parse_qs(access_token)
return access_token['access_token'][0]
def get_graph_api(token):
if isinstance(token, dict):
return facebook.GraphAPI(token['access_token'])
else:
return facebook.GraphAPI(token)
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
self.session_store = sessions.get_store(request=self.request)
try:
webapp2.RequestHandler.dispatch(self)
finally:
self.session_store.save_sessions(self.response)
#webapp2.cached_property
def session(self):
return self.session_store.get_session()
class FBLogin(BaseHandler):
def get(self):
code = self.request.get('code')
if self.request.get("code"):
access_tk = retrieve_access_token(code)
facebookObject = get_graph_api(access_tk)
facebookObject = facebookObject.get_object('me')
self.session["ECommerceUser"] = dict(
idSocial = facebookObject['id'],
image='http://graph.facebook.com/'+facebookObject['id']+'/picture?type=square',
username=last_name[0] + " " + last_name[1],
loginMethod=loginMethod
)
self.redirect("/")
else:
self.redirect(facebook_requestCode)
I set the same redirect URI in the URL login request is set in the Facebook Login settings, as you can see from the code above (i followed this guide: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow)
Any suggestion?