i am trying to change google oauth2 example code from python flask to FastAPI.
this is the code
def oauth2callback():
# Specify the state when creating the flow in the callback so that it can
# verified in the authorization server response.
state = flask.session['state']
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
flow.redirect_uri = flask.url_for('oauth2callback', _external=True)
# Use the authorization server's response to fetch the OAuth 2.0 tokens.
print("this is the flask request url -----------", flask.request.url)
**authorization_response = flask.request.url**
flow.fetch_token(authorization_response=authorization_response)
# Store credentials in the session.
# ACTION ITEM: In a production app, you likely want to save these
# credentials in a persistent database instead.
credentials = flow.credentials
flask.session['credentials'] = credentials_to_dict(credentials)
creds = google.oauth2.credentials.Credentials(
**flask.session['credentials'])
return flask.redirect(flask.url_for('test_api_request'))
the line is authorization_response = flask.request.url
the value of it is "http://localhost:8080/oauth2callback?state=79aCVleeoxdA4bYgo5YnzuK8vsvM22&code=4%2F0AWtgzh611Sc3dhUo_pqQSa4RVTEZOgX9rJHc328XCJ4UmLvHdg5zz3t1k8VS3ihZwKMEA&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.readonly+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.modify+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.labels+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&authuser=0&prompt=none"
now i just want to change this line to the same in fastapi, i have tried request.url.path but it gives me "/oauth2callback" or sometimes None.
The request.url property is a string-like object, and you can get the full URL by casting it as a string.
i.e. str(request.url)
You can also access properties of the URL individually - more information on this can be found in Starlette's documentation.
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])))
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 use the OAuth token and storing it in the session object as I move between different subdomains. So I have the application starting the OAuth1.0a workflow at flask.mydomain.com. It then redirects to sub.mydomain.com/initiate to fetch the request tokens and I want to save the oauth token secret using sessions (not sure if this is the best way), and then authorizes at sub.mydomain.com/authorize. Once the authorization is complete, it goes to the callback but the oauth token secret does not exist in the session. I'm also trying to save the OAuth1Session so that whenever we go to a new route, that data is saved. So I'm not sure how to handle it since the sessions are defined within the scope of the function.
I read that app.secret_key = os.urandom(someval) doesn't make it work which is what I don't have and I made the OAuth1Session to be global originally which doesn't sound like a good idea (happens in the callback).
#app.route("/initiate")
def initiate():
oauth_session = OAuth1Session(client_key=client_key,client_secret=client_secret,callback_uri=callback_uri)
fetch_response = oauth_session.fetch_request_token(request_token_url)
oauth_token = fetch_response.get('oauth_token')
oauth_token_secret = fetch_response.get('oauth_token_secret')
session['oauth_token_secret'] = oauth_token_secret
full_authorization_url = oauth_session.authorization_url(authorize_url, request_token=oauth_token)
return redirect(full_authorization_url)
#app.route("/callback")
def callback():
session.permanent = True
if ('oauth_token_secret' not in session):
return "There is no oauth token secret"
verifier = request.args.get("oauth_verifier")
oauth_token = request.args.get("oauth_token")
oauth = OAuth1Session(
client_key=client_key,
client_secret=client_secret,
resource_owner_key=oauth_token,
resource_owner_secret=session['oauth_token_secret'],
verifier=verifier) # I would like this oauth session to persist until a certain amount of time before having to reauthenticate
fetch_access_tokens= oauth.fetch_access_token(access_token_url)
return redirect(url_for('.getstatus'))
#app.route("/getStatus")
def getstatus():
r = oauth.get(webservice_url + "/statuses")
if r.status_code == 401:
return redirect(url_for('initiate'))
print(r.content)
return r.content
I fixed the issue, it was with the SERVER_NAME config for the application. The SERVER_NAME was set to flask.mydomain.com, so I just removed it entirely