How to send authorization header from html to flask - python

I have a project that use flask in backend and jinja and html for frontend.
What i need is to send a request that has an authorization header and all my routes read that header to see if its from an valid user or not?
def show_admin():
data = request.headers.get('Authorization')
# data = "TOKEN123"
# واکشی اطلاعات مورد نیاز صفحه داشبورد
content = {
'user': mydb.selectalluser(),
'doreh': mydb.selectalldoreh()
}
# چک میشود اگر توکن ارسالی توسط کاربری معتبر است یا خیر
if str(data) == "TOKEN123":
return render_template('admin/dashboard.html', content=content)
# return hello
else:
# اگر توکن معتبر باشد صفحه لود خواهد شد
return render_template('login/index.html')
In the if statement it check if the token is valid or not. but...
1. how to generate a request that included Authorization header
2. how to generate a token for login page

You can't control the client-side request header (i.e. Authorization) on the server-side. IMO, what you want is manage user login/session status, you can implement this with Flask session, the information stored on session can be obtained between requests (use Cookie):
from flask import session
#app.route('/login')
def login():
session['token'] = 'TOKEN123' # store token, use it as a dict
return 'login success'
#app.route('/admin')
def show_admin():
if session.get('token') == "TOKEN123": # get token
return 'admin page'
else:
return 'login page'
However, I would recommend using Flask-Login to handle user session manganment.

Related

Web App MSAL error, 200 error on redirect

Errors:
Access to fetch at 'https://login.microsoftonline.com/{bunchofinfo}' (redirected from 'http://localhost:5000/login') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
GET https://login.microsoftonline.com/{bunchofinfo} net::ERR_FAILED 200
Code:
#app.route("/")
def index():
if not session.get("user"):
return redirect(url_for("login"))
return redirect('http://localhost:3000/')
#app.route("/login")
def login():
session["flow"] = _build_auth_code_flow(scopes=SCOPE_MSAL)
auth_url = session["flow"]["auth_uri"]
print(auth_url)
return redirect(auth_url)
Things Ive tried:
First tried to set {'mode': 'no-cors'} and redirect. But it gives me the HTML for the redirect and doesn't redirect me.
Then tried sending my frontend the auth_url with no redirect. Then window.location.assign(auth_url)
Problem = Once logged on properly, it stays on localhost:5000/login with the auth_url on the page.
Tried to switch code with this but it doesn't move on after login and runs the if not statement.
def login():
if not session.get("user"):
session["flow"] = _build_auth_code_flow(scopes=SCOPE_MSAL)
auth_url = session["flow"]["auth_uri"]
print(auth_url)
return auth_url
return redirect('http://localhost:3000')
Don't know how to proceed. Rest of code from MSAL
#app.route("/getAToken")
def authorized():
try:
cache = _load_cache()
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
session.get("flow", {}), request.args)
if "error" in result:
print(result)
# add error page
# return render_template("auth_error.html", result=result)
session["user"] = result.get("id_token_claims")
_save_cache(cache)
except ValueError: # Usually caused by CSRF
pass # Simply ignore them
return redirect(url_for("index"))
def _load_cache():
cache = msal.SerializableTokenCache()
if session.get("token_cache"):
cache.deserialize(session["token_cache"])
return cache
def _save_cache(cache):
if cache.has_state_changed:
session["token_cache"] = cache.serialize()
def _build_msal_app(cache=None, authority=None):
return msal.ConfidentialClientApplication(
microsoft_client_id, authority=authority or AUTHORITY,
client_credential=client_secret, token_cache=cache)
def _build_auth_code_flow(authority=None, scopes=None):
return _build_msal_app(authority=authority).initiate_auth_code_flow(
scopes or [],
redirect_uri=url_for("authorized", _external=True))
def _get_token_from_cache(scope=None):
cache = _load_cache() # This web app maintains one cache per session
cca = _build_msal_app(cache=cache)
accounts = cca.get_accounts()
if accounts: # So all account(s) belong to the current signed-in user
result = cca.acquire_token_silent(scope, account=accounts[0])
_save_cache(cache)
return result

Get Spotify access token with spotipy on Django and Python

I'm new to Django and I'm trying to link Spotify to my webapp. I'm using Spotify to do it and it correctly access to Spotify.
To do it I have a button that opens the view below
views.py
#authenticated_user
def spotify_login(request):
sp_auth = SpotifyOAuth(client_id=str(os.getenv('SPOTIPY_CLIENT_ID')),
client_secret=str(os.getenv('SPOTIPY_CLIENT_SECRET')),
redirect_uri="http://127.0.0.1:8000/",
scope="user-library-read")
redirect_url = sp_auth.get_authorize_url()
auth_token = sp_auth.get_access_token()
print(auth_token)
print("----- this is the AUTH_TOKEN url -------", auth_token)
return HttpResponseRedirect(redirect_url)
If I don't use auth_token = sp_auth.get_access_token() everything works fine and I got redirected to the correct. Unfortunately, if I add that line of code to access the access token, instead of staying on the same page, it opens another tab on the browser with the Spotify auth_code and lets the original page load forever.
Is there a way to retrieve the access token in the background without making my view reload or open another tab in the browser?
You are being redirected to exactly where you are telling django to go.
redirect_url is just a spotify api redirect containing a code, which is captured and used to get the access token.
Set your expected response as return value.
By the way, keep in mind:
redirect_uri="http://127.0.0.1:8000/", should be added in spotify app (usually as http://127.0.0.1:8000/callback",)
auth_token is a json, you can find token in auth_token['access_token']
The solution was to create a new view to access the URL
views.py
from .utils import is_user_already_auth_spotify, spotify_oauth2
#authenticated_user
def spotify_login(request):
if is_user_already_auth_spotify(request.user.username):
messages.error(request, "You have already linked your Spotify account")
return HttpResponseRedirect('account/' + str(request.user.username))
sp_auth = spotify_oauth2()
redirect_url = sp_auth.get_authorize_url()
return HttpResponseRedirect(redirect_url)
#authenticated_user
def spotify_callback(request):
full_path = request.get_full_path()
parsed_url = urlparse(full_path)
spotify_code = parse_qs(parsed_url.query)['code'][0]
sp_auth = spotify_oauth2()
token = sp_auth.get_access_token(spotify_code)
data = {
str(request.user.username): token
}
with open('spotify_auth.json', 'w') as f:
json.dump(data, f)
messages.success(request, "You have correctly linked your Spotify account")
return HttpResponseRedirect('account/' + str(request.user.username))
urls.py
urlpatterns = [
path('account/<str:username>/', views.account_user, name="account"),
path('spotify_login', views.spotify_login, name="spotify_login"),
path('spotify_callback', views.spotify_callback, name="spotify_callback"),
]
utils.py
import json
from spotipy import oauth2
import os
def is_user_already_auth_spotify(username):
my_loaded_dict = {}
with open('spotify_auth.json', 'r') as f:
try:
my_loaded_dict = json.load(f)
except:
# file vuoto
pass
if str(username) in my_loaded_dict:
# controllare scadenza token ed in caso rinnovarlo
return True
else:
return False
def spotify_oauth2():
sp_auth = oauth2.SpotifyOAuth(client_id=str(os.getenv('SPOTIPY_CLIENT_ID')),
client_secret=str(os.getenv('SPOTIPY_CLIENT_SECRET')),
redirect_uri="http://127.0.0.1:8000/members/spotify_callback",
scope="user-library-read")
return sp_auth
The code also saves the token in a JSON and search for it if it has already been saved

Heroku app having trouble with Spotify API token

So I've been using Flask to create an app that uses Spotify API, and the authorization code flow works perfectly fine when running on my localhost server. However, after deploying the app to Heroku, occasionally the app will crash and give me a 'token not provided' error in the logs. Sometimes it'll work flawlessly, but when I play around with it more it'll crash. I try to redo the process on my localhost but it doesn't seem to break. I'm storing the token in sessions, could it be that Heroku is having problem with retrieving session variables?
Here's the flask app code
#Home view
#app.route('/')
#app.route('/home')
def home():
return render_template('home.html', title='Home')
#Login view
#app.route('/login', methods=['GET','POST'])
def login():
AUTH_FIRST = req_auth()
return redirect(AUTH_FIRST)
#Callback view for Spotify API
#app.route('/callback')
def callback():
if request.args.get('code'):
code = request.args.get('code')
token = req_token(code)
session['token'] = token
return redirect(url_for('generate_playlist'))
else:
return redirect(url_for('home'))
#Generate playlist view
#app.route('/generate_playlist', methods=['GET', 'POST'])
def generate_playlist():
if request.method == 'POST':
levels = int(float(request.form['level']))
token = session.get('token')
generate(token, levels)
return redirect(url_for('success'))
else:
return redirect(url_for('generate_playlist'))
This is the backend authorization code (I've commented out some parts to make it simpler, yet it still doesn't work):
client_id = os.environ.get("CLIENT_ID")
client_secret = os.environ.get("CLIENT_SECRET")
redirect_uri = os.environ.get('REDIRECT_URI')
state = ''.join(random.choice(string.ascii_lowercase + string.digits) for n in range(8))
scope = 'user-top-read user-library-read playlist-modify-public'
def req_auth():
show_dialog = "false"
AUTH_FIRST_URL = f'https://accounts.spotify.com/authorize?client_id={client_id}&response_type=code&redirect_uri={quote("https://surprisify-playlist-generator.herokuapp.com/callback")}&show_dialog={show_dialog}&state={state}&scope={scope}'
return AUTH_FIRST_URL
def req_token(code):
#B64 encode variables
client_creds = f"{client_id}:{client_secret}"
client_creds_b64 = base64.b64encode(client_creds.encode())
#Token data
token_data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": "https://surprisify-playlist-generator.herokuapp.com/callback"
}
#Token header
token_header = {
"Authorization": f"Basic {client_creds_b64.decode()}"
}
#Make request post for token info
token_json = requests.post('https://accounts.spotify.com/api/token', data=token_data, headers=token_header)
return token_json.json()['access_token']
#Checking if token is still valid, otherwise, refresh
# if token_json.json()['expires_in']:
# now = datetime.datetime.now()
# expires_in = token_json.json()['expires_in']
# expires = now + datetime.timedelta(seconds=expires_in)
# if now > expires:
# refresh_token_data = {
# "grant_type": "refresh_token",
# "refresh_token": token_json.json()['refresh_token']
# }
# refresh_token_json = requests.post('https://accounts.spotify.com/api/token', data=refresh_token_data, headers=token_header)
# token = refresh_token_json.json()['access_token']
# return token
# else:
# token = token_json.json()['access_token']
# return token
# else:
# token = token_json.json()['access_token']
# return token
Could the error happen after you restart the browser? Flask session cookies are stored (by default) until you close the browser. Then, if you don't authenticate again, calling session.get(token) will return None and is likely why you get the error. You could try checking if token is None in generate_playlist() and requiring re-authentication with a redirect.
Here is an implementation of the Spotify authorization code flow using Spotipy that I have used with success in my own project: https://stackoverflow.com/a/57929497/6538328 (method 2).

In flask, why request header is None in app.before_request()?

When I try to access the header of a request in a function adnotated with #app.before_request, it is always None. Anyone had this problem before?
Here is the function:
#app.before_request
def verifyToken():
if request.endpoint in ['myEndpoint']
auth = request.headers.get('Authorization')
if auth.startswith('Bearer '):
jwtToken = auth[7:]
try:
decoded = jwt.decode(jwtToken, 'secret_key', algorithms=['HS256'])
except jwt.ExpiredSignatureError as e:
responseObject = {
"status": "failed",
"message": "Token has expired. Please login again."
}
return jsonify(responseObject), 401
I was being 5 months in Flask, I discover Flask Embeded Middleware fires up 2 times, the first one got None value and the second one is the client request and each fire up has different request.method, the OPTIONS and <DEFAULT METHOD> of the client, why there is an OPTIONS method first in before...request and after...request befoe goes in client request?, I'm still searching for reference
and that is why I just catch it through conditional statement if request.method != 'OPTIONS': and here how it is look like
from flask import request
def my_before_request():
if request.method !== 'OPTIONS'
token = request.headers['<header var>']
# do something...
# same as after request
The OPTIONS method could have been triggered by the browser due to request being preflighted. This is done to determine if the actual request is safe to send. Refer CORS

Trouble using bottle-jwt decorator (not works)

Having some trouble using this plugin https://github.com/agile4you/bottle-jwt/
It seems to not work as I expected, down below my code:
import bottle
from Py.engine import *
from bottle_jwt import (JWTProviderPlugin, jwt_auth_required)
class AuthBackend(object):
user = {'id': 1237832, 'username': 'pav', 'password': '123', 'data': {'sex': 'male', 'active': True}}
def authenticate_user(self, username, password):
"""Authenticate User by username and password.
Returns:
A dict representing User Record or None.
"""
if username == self.user['username'] and password == self.user['password']:
return self.user
return None
def get_user(self, user_id):
"""Retrieve User By ID.
Returns:
A dict representing User Record or None.
"""
if user_id == self.user['id']:
return {k: self.user[k] for k in self.user if k != 'password'}
return None
app = bottle.Bottle()
server_secret = 'secret'
provider_plugin = JWTProviderPlugin(
keyword='jwt',
auth_endpoint='/login',
backend=AuthBackend(),
fields=('username', 'password'),
secret=server_secret,
ttl=30
)
app.install(provider_plugin)
#app.route('/')
#jwt_auth_required
def index():
return open('Html/index.html', 'r').read()
#app.post('/login')
def login():
return open('Html/login.html', 'r').read()
#app.get('/login')
def login():
return open('Html/login.html', 'r').read()
def run_server():
bottle.run(app=app, host='localhost', port=8080, debug=True, reloader=True)
# Main
if __name__ == '__main__':
run_server()
Once running, if I open browser On 127.0.0.1/8080 i get back a blank page with the string "{"AuthError": ["Cannot access this resource!"]}"
Which is Fine, it means that I'm not allowed to open index.html file (Cool: #jwt_auth_required worked)
Digging in source file I found a function named validate_token() with:
if not token:
logger.debug("Forbidden access")
raise JWTForbiddenError('Cannot access this resource!')
Here is the exception
except JWTForbiddenError as error:
bottle.response.content_type = b('application/json')
bottle.response._status_line = b('403 Forbidden')
return {"AuthError": error.args}
So, is there any way to redirect me on my login.html page if token does not match or is absent?
Plugin includes some way to do that or is just an API pckg?
That's not how JWT concept is supposed to be used. JWT are for RESTFul.
You need to make the server as REST API and on the client use JS
libraries such as AngularJs / Vue.js etc.,
Coming to the question about the plugin:
provider_plugin = JWTProviderPlugin(
keyword='jwt',
auth_endpoint='/login',
backend=AuthBackend(),
fields=('username', 'password'),
secret=server_secret,
ttl=30
)
auth_endpoint='/login' is to give a custom endpoint for authorization where the Bottle_JWT methods are looking for credentials to validate and generate JWT for.
I created a mock just to construct a response and this is how it should be used.
Once you pass the correct credential, the plugin responds with the JWT and expire which you have to intercept in authorized calls and add as request headers
Hope this helps.

Categories

Resources