I have been looking for an explanation on how to do this using this particular set of libraries and couldn't really find anything simple and straightforward enough. Since I have figured out a working way myself, I decided to drop it here for people who might be also be looking for this (other beginners like me).
Please feel free to suggest a better solution if one comes to mind!
from flask import Flask, flash, redirect, render_template, request, session, url_for
from twython import Twython, TwythonAuthError, TwythonError, TwythonRateLimitError
from flask_session import Session
from tempfile import mkdtemp
# configure application
app = Flask(__name__)
app.config["SESSION_FILE_DIR"] = mkdtemp()
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
app.config['SECRET_KEY'] = 'your app secret key'
APP_KEY = 'Your Twitter App Key'
APP_SECRET = 'Your Twitter App Secret'
#app.route('/', methods=["GET", "POST"])
def index():
if request.method == 'POST':
twitter = Twython(APP_KEY, APP_SECRET)
auth = twitter.get_authentication_tokens(callback_url='http://yourwebsite.com/login')
session['OAUTH_TOKEN'] = auth['oauth_token']
session['OAUTH_TOKEN_SECRET'] = auth['oauth_token_secret']
return redirect(auth['auth_url'])
else:
return render_template('index.html')
#app.route('/login')
def login():
oauth_verifier = request.args.get('oauth_verifier')
OAUTH_TOKEN=session['OAUTH_TOKEN']
OAUTH_TOKEN_SECRET=session['OAUTH_TOKEN_SECRET']
twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
final_step = twitter.get_authorized_tokens(oauth_verifier)
session['OAUTH_TOKEN'] = final_step['oauth_token']
session['OAUTH_TOKEN_SECRET'] = final_step['oauth_token_secret']
OAUTH_TOKEN = session['OAUTH_TOKEN']
OAUTH_TOKEN_SECRET = session['OAUTH_TOKEN_SECRET']
session['twitter'] = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
flash("You've logged in!")
return redirect('/')
When website is accessed via link, render_template('index.html') renders a dynamic site that contain a "Log In with Twitter" button (form with a POST method) if user not logged in, or content otherwise. At the end of the login route I create a Twython session variable for application wide use to access Twitter data, and then redirect user back to index page (to clear the url that contains authentifier parameters). That's it, this works for me well as of now.
Related
I have a Flask app where I want to create playlists using Spotify API. My issue is similar to this Stackoverflow question.
The difference is that I am using OAuthlib instead of requests and the solution posted there didn't work in my case.
The problem
In the http request, when I set data={'name': 'playlist_name', 'description': 'something'},
I am getting a response: "error": {"status": 400,"message": "Error parsing JSON."}
But when I follow the answer mentioned above and try this: data=json.dumps({'name': 'playlist_name', 'description': 'something'}),
I am getting following error in the console: "ValueError: not enough values to unpack (expected 2, got 1)".
How I can fix this? Here is a simplified version of my app:
app.py
from flask import Flask, url_for, session
from flask_oauthlib.client import OAuth
import json
app = Flask(__name__)
app.secret_key = 'development'
oauth = OAuth(app)
spotify = oauth.remote_app(
'spotify',
consumer_key=CLIENT,
consumer_secret=SECRET,
request_token_params={'scope': 'playlist-modify-public playlist-modify-private'},
base_url='https://accounts.spotify.com',
request_token_url=None,
access_token_url='/api/token',
authorize_url='https://accounts.spotify.com/authorize'
)
#app.route('/', methods=['GET', 'POST'])
def index():
callback = url_for(
'create_playlist',
_external=True
)
return spotify.authorize(callback=callback)
#app.route('/playlist', methods=['GET', 'POST'])
def create_playlist():
resp = spotify.authorized_response()
session['oauth_token'] = (resp['access_token'], '')
username = USER
return spotify.post('https://api.spotify.com/v1/users/' + username + '/playlists',
data=json.dumps({'name': 'playlist_name', 'description': 'something'}))
#spotify.tokengetter
def get_spotify_oauth_token():
return session.get('oauth_token')
if __name__ == '__main__':
app.run()
You are using the data parameter, which takes a dict object, but you are dumping it to a string, which is not necessary. Also, you have to set the format to json, as follow:
#app.route('/playlist', methods=['GET', 'POST'])
def create_playlist():
resp = spotify.authorized_response()
session['oauth_token'] = (resp['access_token'], '')
username = USER
return spotify.post('https://api.spotify.com/v1/users/' + username + '/playlists',
data={'name': 'playlist_name', 'description': 'something'}, format='json')
Looking at the Flask-Login docs and several relatively old threads on Stackoverflow, I am concerned about the security of my solution for passing the restricted page URL through the login process.
First, I was getting Attribution Error when trying to use #login_manager.unauthorized_handler. ("login_manager does not have an attribute unauthorized_handler.") This a separate issue altogether, because it really should have worked. (See app factory below.)
When I applied a redirect_destination function without the modified #login_manager.unauthorized_handler, the login failed to redirect to the target destination.
def redirect_destination(dest_url, fallback):
try:
dest_url = url_for(dest_url)
except:
return redirect(fallback)
return redirect(dest_url)
Then I applied a session instance to be passed from the destination URL through login along with the fresh_login_required decorator and .is_authenticated property instead of the standard login_required.
target blueprint:
#target.route('/target', methods=['GET', 'POST'])
#fresh_login_required
def target():
if current_user.is_authenticated:
...
return render_template('target.html')
else:
session['dest_url']=request.endpoint
return redirect(url_for('account.login'))
auth blueprint:
#account.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
dest_url = session.get('dest_url')
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if not user:
return redirect(url_for('account.login'))
if user.check_password(form.password.data):
login_user(user, remember=form.remember_me.data)
...
return redirect_destination(dest_url, fallback=url_for('main.index'))
else:
return redirect(url_for('account.login'))
return render_template('account/login.html')
app factory:
...
login_manager = LoginManager()
login_manager.login_view = 'account.login' #hard-coded view
login_manager.refresh_view = 'account.refresh' #hard-coded view
def create_app():
app = Flask(__name__,
static_url_path='/',
static_folder='../app/static',
template_folder='../app/templates')
db.init_app(app)
login_manager.init_app(app)
from app import models
from .templates.account import account
from .templates.main import main
from .templates.target import target
app.register_blueprint(main)
app.register_blueprint(account)
app.register_blueprint(target)
return app
So, this solution works, and it can be applied to multiple blueprints. My concern is that I am missing some important details that necessitated other, more complex solutions. Is there a security weak point? Other issues?
References:
https://flask-login.readthedocs.io/en/latest/
How do I pass through the "next" URL with Flask and Flask-login?
Flask/Werkzeug, how to return previous page after login
Flask how to redirect to previous page after successful login
Get next URL using flask redirect
Flask Basic HTTP Auth use login page
Following are the code files with relevant code snippets:
init.py:
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
app.config['SECRET_KEY'] = 'super-secret'
In auth.py:
def authenticate_user(login, password):
'''
Return dict or None after checking against db for valid user
'''
s = select([users]).where(users.c.email==login)
result_set = conn.execute(s)
if result_set.rowcount == 1:
for r in result_set:
print r[users.c.password], 'result_set[users.c.password]'
if pwd_context.verify(password, r[users.c.password]):
# There is only one unique email/password pair
print 'matched'
return dict(r)
else:
return None
return
How to get the access_token value for the user on login? I have installed Flassk-JWT in the virtualenv and followed this doc: https://pythonhosted.org/Flask-JWT/ But please note I am not using OOPs ie. User class etc. I am using sqlalchemy core with Flask and python. To further use this token, I need to call it as a decorator for the API call is what I understand as:
#app.route('/rt/api/v1.0/list', methods=['GET'])
#jwt_required()
In views.py:
from myapp.auth import authenticate_user
#app.route('/auth', methods=['POST','GET'])
def login():
email = request.form["email"]
password = request.form["password"]
if request.method == 'POST':
result_set = authenticate_user(email, password)
if result_set:
session['email'] = result_set['email']
user_dict = result_set
if user_dict:
session['email'] = user_dict['email']
jwt = JWT(app, user_dict['email'], user_dict["id"])
How to exactly connect the various code files to get the access token value is what I am stuck up with.Please guide. Also Wish to exclude the login API request from the before_request callback(). All other APIs can have the before and after_request callbacks() executed.
Finally found a way better implementation with the basic usage on readthedocs
I would like to return the token as a string to another python script. However I'm stuck at the callback page. I made the redirect_uri to "/callback" and it returns the authorization code in the URL as
www.my.website.com/callback?code={code}.
The Facebook OAuth 2 example at http://requests-oauthlib.readthedocs.org/en/latest/examples/facebook.html. I keep getting a 404 error by just pasting the redirect URI.
I would also like to request the following parameters: {id, message, created_time} but I'm not sure how.
import os
from requests_oauthlib import OAuth2Session
from flask import Flask, request, redirect, session, render_template, url_for
from requests_oauthlib.compliance_fixes import facebook_compliance_fix
app = Flask(__name__)
app.secret_key = os.urandom(24)
#app.route("/login")
def demo():
client_id = '1624399237811291'
client_secret = 'SECRET'
# OAuth endpoints given in the Facebook API documentation
authorization_base_url = 'https://www.facebook.com/dialog/oauth'
token_url = 'https://graph.facebook.com/oauth/access_token'
redirect_uri = 'http://www.bbg.mybluemix.net/callback' # Should match Site URL
facebook = OAuth2Session(client_id, redirect_uri=redirect_uri)
facebook = facebook_compliance_fix(facebook)
# Redirect user to Facebook for authorization
authorization_url, state = facebook.authorization_url(authorization_base_url)
authurl = '%s' % authorization_url
return render_template('welcome.html', authurl=authurl)
# Get the authorization verifier code from the callback url
#app.route("/callback", methods=["GET"])
def callback():
# Fetch the access token
redirect_response = ' '
token = facebook.fetch_token(token_url, client_secret=client_secret,
authorization_response=redirect_response)
return '%s' % token
# Fetch a protected resource, i.e. user profile
#r = facebook.get('https://graph.facebook.com/me?')
#return '%s' % r.content
I have built a website using flask (www.csppdb.com). Sometimes when I log in as one user, log out, then login as another user I still see pages from the first user I logged in as. This problem is immediately fixed when the page is refreshed. I think this is called "caching" if I am not mistaken. Is there any way I could disable this on a site wide level so that every page that is visited needs a new refresh?
It would be like sharing your computer with a friend. He logs into Facebook, then logs out. Now you log in on his computer and you see his profile... (awkward). After you refresh the page the problem is fixed.
Here is some of my code. I was using flask-login but I then tried to "roll my own"
from flask.ext.mysql import MySQL
import os
from flask import Flask, request, jsonify, session, url_for, redirect, \
render_template, g, flash
from data import *
from werkzeug import check_password_hash, generate_password_hash
import config
app = Flask(__name__)
mysql = MySQL()
app.config['MYSQL_DATABASE_HOST'] = os.environ['MYSQL_DATABASE_HOST'] if 'MYSQL_DATABASE_HOST' in os.environ else config.MYSQL_DATABASE_HOST
app.config['MYSQL_DATABASE_PORT'] = os.environ['MYSQL_DATABASE_PORT'] if 'MYSQL_DATABASE_PORT' in os.environ else config.MYSQL_DATABASE_PORT
app.config['MYSQL_DATABASE_USER'] = os.environ['MYSQL_DATABASE_USER'] if 'MYSQL_DATABASE_USER' in os.environ else config.MYSQL_DATABASE_USER
app.config['MYSQL_DATABASE_PASSWORD'] = os.environ['MYSQL_DATABASE_PASSWORD'] if 'MYSQL_DATABASE_PASSWORD' in os.environ else config.MYSQL_DATABASE_PASSWORD
app.config['MYSQL_DATABASE_DB'] = os.environ['MYSQL_DATABASE_DB'] if 'MYSQL_DATABASE_DB' in os.environ else config.MYSQL_DATABASE_DB
mysql.init_app(app)
if 'SECRET_KEY' in os.environ: app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
else: app.config['SECRET_KEY'] = os.urandom(24)
def connect_db(): return mysql.connect()
def check_auth():
g.user = None
if 'username' in session:
g.user = get_user(session['username'])
return
return redirect(url_for('login'))
#app.route('/')
def home():
if 'username' in session: return redirect(url_for('main'))
return render_template('home.html')
def connect_db(): return mysql.connect()
#app.teardown_request
def teardown_request(exception):
if exception: print exception
g.db.close()
#app.before_request
def before_request():
print session.keys(), session.values()
print("before request")
print ('username' in session, "in session?")
g.db = connect_db()
g.user = None
if "username" in session:
g.user = get_user(session['username'])
#app.route('/login/', methods=['GET', 'POST'])
def login():
"""Logs the user in."""
if 'username' in session:
return redirect(url_for('main'))
error = None
if request.method == 'POST':
print("login hit")
user = get_user(request.form['username'])
if user is None:
error = 'Invalid username'
print error
elif not check_password_hash(user.password, request.form['password']):
error = 'Invalid password'
print error
else:
flash('You were logged in')
print "logged in"
session['username'] = request.form['username']
g.user = request.form['username']
print error, "error"
return redirect(url_for('main'))
return render_template('login.html', error=error)
Setting the cache to be max-age=0 fixed it.
#app.after_request
def add_header(response):
"""
Add headers to both force latest IE rendering engine or Chrome Frame,
and also to cache the rendered page for 10 minutes.
"""
response.headers['X-UA-Compatible'] = 'IE=Edge,chrome=1'
response.headers['Cache-Control'] = 'public, max-age=0'
return response
To stop browser caching on these sort of pages you need to set some HTTP response headers.
Cache-Control: no-cache, no-store
Pragma: no-cache
Once you do this then the browser wont cache those pages. I dont know how to do this with "flask" so I will leave that as an exercise for you :)
This question shows how to add a response header Flask/Werkzeug how to attach HTTP content-length header to file download