redis database for flask application - python

I am trying to modify an existing flask application to use a redis database instead of tokens. I searched for an example and through redis documentation but could not find anything. Here is what I have:
from flask import Flask, Response
from flask.ext.login import LoginManager, UserMixin, login_required
from redis import Redis
redis = Redis()
app = Flask(__name__)
login_manager = LoginManager()
login_manager.init_app(app)
class User(UserMixin):
# proxy for a database of users
user_database = {"JohnDoe": ("JohnDoe", "John"),
"JaneDoe": ("JaneDoe", "Jane")}
def __init__(self, username, password):
self.id = username
self.password = password
#classmethod
def get(cls,id):
return cls.user_database.get(id)
#login_manager.request_loader
def load_user(request):
token = request.headers.get('Authorization')
if token is None:
token = request.args.get('token')
if token is not None:
username,password = token.split(":") # naive token
user_entry = User.get(username)
if (user_entry is not None):
user = User(user_entry[0],user_entry[1])
if (user.password == password):
return user
return None
#app.route("/",methods=["GET"])
def index():
return Response(response="Hello World!",status=200)
#app.route("/protected/",methods=["GET"])
#login_required
def protected():
return Response(response="Hello Protected World!", status=200)
if __name__ == '__main__':
app.config["SECRET_KEY"] = "ITSASECRET"
app.run(port=5000,debug=True)
Can anyone point me in the right direction in terms of using redis as a simple authorization database?

This isn't much of a specific suggestion, but there is pretty good documentation for Flask-Cache. It provides numerous ways to integrate Redis (or another cache) into a flask application. I am not sure if this will help you with your purpose, but in any case, it will eliminate some boiler plate.

Related

Flask-login on Apache sets user to anonymous after a few clicks and a few page reloads

NOTE: This issue only appears to happen when I am running Flask with WSGI on Apache. When I run it via flask run --host=0.0.0.0 I get no issues whatsoever.
I realize that there are many other questions with a similar issue, and I have tried applying the different recommendations, in particular the following:
setting:
login_user(form.user, remember=True, force=True) # Tell flask-login to log them in.
session.permanent = True
app.permanent_session_lifetime = timedelta(seconds=3600)
session.modified = True
As well as: app.config["REMEMBER_COOKIE_DURATION"] = timedelta(seconds=3600).
I am running Flask on Apache 2.4 with Python 3.6.
Here's the code for my app:
import sys
import os
import os.path
import ssl
import json
from datetime import datetime, timedelta
from flask import Flask, make_response, url_for, render_template, jsonify, redirect, request, session
from flask_ldap3_login import LDAP3LoginManager
from flask_login import LoginManager, login_user, logout_user, UserMixin, current_user
from flask_security import login_required
from flask_session import Session
from flask_ldap3_login.forms import LDAPLoginForm
from ldap3 import Tls
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
app.config['DEBUG'] = True
# Hostname of your LDAP Server
app.config['LDAP_HOST'] = 'dc.website.com'
# Port number of your LDAP server
app.config['LDAP_PORT'] = 636
# Specify the server connection should use SSL
app.config['LDAP_USE_SSL'] = True
# Base DN of your directory
app.config['LDAP_BASE_DN'] = 'CN=Users,DC=website,dc=com'
# Users DN to be prepended to the Base DN
app.config['LDAP_USER_DN'] = ''
# Groups DN to be prepended to the Base DN
app.config['LDAP_GROUP_DN'] = ''
# The RDN attribute for your user schema on LDAP
app.config['LDAP_USER_RDN_ATTR'] = 'cn'
# The Attribute you want users to authenticate to LDAP with.
app.config['LDAP_USER_LOGIN_ATTR'] = 'sAMAccountName'
# The Username to bind to LDAP with
app.config['LDAP_BIND_USER_DN'] = 'CN=LDAP Read-only,CN=Users,DC=website,dc=com'
# The Password to bind to LDAP with
app.config['LDAP_BIND_USER_PASSWORD'] = 'password'
app.config["REMEMBER_COOKIE_DURATION"] = timedelta(seconds=3600)
login_manager = LoginManager(app) # Setup a Flask-Login Manager
ldap_manager = LDAP3LoginManager(app) # Setup a LDAP3 Login Manager.
# Initialize a `Tls` context, and add the server manually. See
# http://ldap3.readthedocs.io/ssltls.html for more information.
tls_ctx = Tls(
validate=ssl.CERT_REQUIRED,
version='ssl.PROTOCOL_TLSv1.3',
ca_certs_file='/usr/local/share/ca-certificates/cert.crt',
valid_names=[
'dc.website.com',
]
)
ldap_manager.add_server(
app.config.get('LDAP_HOST'),
app.config.get('LDAP_PORT'),
app.config.get('LDAP_USE_SSL'),
tls_ctx=tls_ctx
)
# Create a dictionary to store the users in when they authenticate
# This example stores users in memory.
users = {}
# Declare an Object Model for the user, and make it comply with the
# flask-login UserMixin mixin.
class User(UserMixin):
def __init__(self, dn, username, data):
self.dn = dn
self.username = username
self.data = data
def __repr__(self):
return self.dn
def get_id(self):
return self.dn
# Declare a User Loader for Flask-Login.
# Simply returns the User if it exists in our 'database', otherwise
# returns None.
#login_manager.user_loader
def load_user(id):
if id in users:
return users[id]
return None
# Declare The User Saver for Flask-Ldap3-Login
# This method is called whenever a LDAPLoginForm() successfully validates.
# Here you have to save the user, and return it so it can be used in the
# login controller.
#ldap_manager.save_user
def save_user(dn, username, data, memberships):
user = User(dn, username, data)
users[dn] = user
return user
# Declare some routes for usage to show the authentication process.
#app.route('/')
def home():
# Redirect users who are not logged in.
if not current_user or current_user.is_anonymous:
return redirect(url_for('login'))
return render_template('index.html')
#app.route('/login', methods=['GET', 'POST'])
def login():
# Instantiate a LDAPLoginForm which has a validator to check if the user
# exists in LDAP.
form = LDAPLoginForm()
if form.validate_on_submit():
# Successfully logged in, We can now access the saved user object
# via form.user.
login_user(form.user, remember=True, force=True) # Tell flask-login to log them in.
session.permanent = True
app.permanent_session_lifetime = timedelta(seconds=3600)
session.modified = True
return redirect('/') # Send them home
return render_template('login.html', form=form, current_user=current_user)
#app.route('/logout')
def logout():
logout_user()
return redirect(url_for('login'))
if __name__ == '__main__':
app.run()
And here's the WSGI file:
#!/usr/bin/python3.6
import logging
import sys
logging.basicConfig(stream=sys.stderr)
sys.path.insert(0, '/opt/flaskapp/')
from flaskapp import app as application
application.secret_key = 'secret'
Sometimes the user gets logged out after a few seconds, other times the session can last an hour. When I check the browser's memory (in Firefox, Chrome and Edge) the session cookies are still there.
Am I doing something wrong? I've also tried checking whether or not the users array becomes empty. It does. Even if I make some kind of a try check before the line users = {}.
The issue was with the WSGI file, as it is Apache that handles the sessions.
The line app.permanent_session_lifetime = timedelta(seconds=3600) from the application code should actually be in the WSGI file, so that it should look like this:
#!/usr/bin/python3.6
import logging
import sys
from datetime import datetime, timedelta
logging.basicConfig(stream=sys.stderr)
sys.path.insert(0, '/opt/flaskapp/')
from flaskapp import app as application
application.secret_key = 'secret'
application.permanent_session_lifetime = timedelta(seconds=3600)

Can we use JWT with sqlalchemy in flask api as below?

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

How to merge Flask login with a Dash application?

I have to design a web-app that provides Flask services and Dash services. For example I would like to create a login in Flask, combined with a Dash application. The problem is that I can't bind the flask login with dash. I would need a method like '#require_login' that filters access to even Dash services.
The code is as follows:
app_flask = Flask(__name__)
app_flask.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////login.db'
app_flask.config['SECRET_KEY'] = 'thisissecret'
db = SQLAlchemy(app_flask)
login_manager = LoginManager()
login_manager.init_app(app_flask)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(30), unique=True)
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
#app_flask.route('/')
def index():
user = User.query.filter_by(username='admin').first()
login_user(user)
return 'You are now logged in!'
#app_flask.route('/logout')
#login_required
def logout():
logout_user()
return 'You are now logged out!'
#app_flask.route('/home')
#login_required
def home():
return 'The current FLASK user is ' + current_user.username
# TODO how to add login_required for dash?
app_dash = Dash(server=app_flask, url_base_pathname='/dash/')
app_dash.layout = html.H1('MY DASH APP')
if __name__ == '__main__':
app_dash.run_server(debug=True)
This line, app_dash = Dash(server=app_flask, url_base_pathname='/dash/'), creates new view_functions in app_flask identified by its url_base_pathname.
You can debug and inspect the value of app_flask.view_functions before and after the creation of app_dash.
Now that we know which view_functions are created by app_dash, we can apply login_required to them manually.
for view_func in app_flask.view_functions:
if view_func.startswith(app_dash.url_base_pathname):
app_flask.view_functions[view_func] = login_required(app_flask.view_functions[view_func])
The `app_dash` endpoints will now be protected.
It would be better if you block all requests by using #app.before_request, and only allow the request if logged in or if the endpoint is marked as public.
def check_route_access():
if request.endpoint is None:
return redirect("/login")
func = app.view_functions[request.endpoint]
if (getattr(func, "is_public", False)):
return # Access granted
# check if user is logged in (using session variable)
user = session.get("user", None)
if not user:
redirect("/login")
else:
return # Access granted```
Now all endpoints will be checked, even dash app endpoints.
Add this decorator named public_route:
def public_route(function):
function.is_public = True
return function
And add the decorator to the public methods, like login, error pages etc.
#public_route
#app.route("/login")
def login():
# show/handle login page
# call did_login(username) when somebody successfully logged in
def did_login(username):
session["user"] = username
This way you never need the #login_required anymore because all endpoints require login unless stated otherwise by #public_route.
A solution : session from flask (work with cookie)
from flask import session
it's an exemple :
#login_manager.user_loader
def load_user(user_id):
# I think here it's good
session["uid"] = user_id
return User.query.get(int(user_id))
# TODO how to add login_required for dash?
if "uid" in session :
app_dash = Dash(server=app_flask, url_base_pathname='/dash/')
app_dash.layout = html.H1('MY DASH APP')

Is it more secure to use itsdangerous with Flask-Login without the remember me option? Why or why not?

I am learning how to create a Flask app using a MongoDB database by putting together a user authentication system. When I went through the code with my superior at work, he said using werkzeug.security for hashing passwords may not be a good idea and asked me to make use of itsdangerous library in my code (below).
What has been driving me insane is I couldn't find anywhere online why using werkzeug.security on its own is a bad idea. As far as I can gather itsdangerous package lets you use a login serialiser to encrypt and decrypt the cookie token when you use the remember me option in Flask-Login (I am not using this option). I found this article explaining Flask-Login tokens.
Is it still important to use itsdangerous even if I don't use the remember me option?
from flask import Flask, url_for, redirect, render_template, request
from flask_mongoengine import MongoEngine
from wtforms import form, fields, validators
from werkzeug.security import generate_password_hash, check_password_hash
import flask_login
# Create application
app = Flask(__name__)
# Create a secret key so we can use sessions
app.config['SECRET_KEY'] = 'super-secret'
# MongoDB settings
app.config['MONGODB_SETTINGS'] = {'DB': 'mymongodb'}
db = MongoEngine()
db.init_app(app)
# Create user document (MongoDB documents are like rows in a relational table).
class User(db.Document):
login = db.StringField(max_length=80, unique=True)
password = db.StringField(max_length=80)
email = db.StringField(max_length=80)
name = db.StringField(max_length=80)
# Flask-Login integration
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
# Define login and registration forms (for flask-login)
class LoginForm(form.Form):
login = fields.StringField(validators=[validators.required()])
password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field):
user = self.get_user()
if user is None:
raise validators.ValidationError('Invalid username.')
if not check_password_hash(user.password, self.password.data):
raise validators.ValidationError('Invalid password.')
def get_user(self):
return User.objects(login=self.login.data).first()
class RegistrationForm(form.Form):
login = fields.StringField(validators=[validators.required(),validators.length(min=3, max=80)])
password = fields.PasswordField(validators=[validators.required(), validators.length(min=6, max=80)])
email = fields.StringField(validators=[validators.required(),validators.email(), validators.length(min=6, max=80)])
name = fields.StringField(validators=[validators.required(), validators.length(min=3, max=80)])
def validate_login(self, field):
if User.objects(login=self.login.data):
raise validators.ValidationError('Duplicate username.')
# Initialise flask-login
def init_login():
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
# Create user loader function
#login_manager.user_loader
def load_user(user_id):
return User.objects(id=user_id).first()
# Flask views
#app.route('/', methods=('GET', 'POST'))
#app.route('/login/', methods=('GET', 'POST'))
def login_view():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
user = form.get_user()
flask_login.login_user(user)
return render_template('main.html', user=flask_login.current_user)
return render_template('form.html', form=form)
#app.route('/register/', methods=('GET', 'POST'))
def register_view():
form = RegistrationForm(request.form)
if request.method == 'POST' and form.validate():
hashpass = generate_password_hash(form.password.data, method='sha256', salt_length=8)
user = User(form.login.data, hashpass, form.email.data, form.name.data)
user.save()
flask_login.login_user(user)
return render_template('main.html', user=flask_login.current_user)
return render_template('form.html', form=form)
#app.route('/logout/')
def logout_view():
flask_login.logout_user()
return redirect(url_for('login_view'))
#app.route('/main/')
def main_view():
if flask_login.current_user.is_authenticated:
return render_template('main.html', user=flask_login.current_user)
return redirect(url_for('login_view'))
#app.route('/map/')
def map_view():
if flask_login.current_user.is_authenticated:
return render_template('map.html', user=flask_login.current_user)
return redirect(url_for('login_view'))
if __name__ == '__main__':
# Initialise flask-login
init_login()
# Start the Flask app
app.run(debug=True)
Thanks,
Aina.
generate_password_hash is a function for securely hashing passwords. ItsDangerous is a library for securely signing (but not hashing or encrypting) arbitrary data. ItsDangerous would not be appropriate for hashing passwords.
Since you're using Flask-Login, it will handle cookies for you. You would not use ItsDangerous directly in that case. Flask uses ItsDangerous behind the scenes to sign the session cookie.

How do I handle login in flask with multiple blueprints?

I have multiple blueprints that needs to be integrated into a single app. I'm using flask-login to handle logins. However I'm confused on how to handle the LoginManager() and the .user_loader for my blueprints.
This is my current file structure.
system/
run.py
config.py
app/
__init__.py
models.py
views/
blueprint1.py
blueprint2.py
static/
templates/
<templates>
What's the correct way to implement them? Do I just call them at the __init__.py and import the login manager variable at the blueprints? or Do I need to call them individually in the blueprints?
Hopefully I'm able to portray the question clearly. Thank you for reading and answering
You must understand that for one application you must use one login manager no matter how many blueprints you use (of course there can be specific exceptions for example when blueprints are independent, but in this case you probably can't use flask-login). Because:
You have 1 entry point
If user is not logged in, he will be redirected to login/registration page
You have 1 user loader
How login manager works:
It registers current_user in request context
before_request reads your session, gets user id, loads the user with user_loader and set it to current_user or AnonymousUser
When you visit the private page, login_required checks current_user.is_authenticated() else redirects to login page
On login, it adds user id to the session
So you must initialize only one login manager instance for flask application and then use login_required and current_user in all your blueprints.
This is how I have handled it:
This is where I am initialising everything:
import logging
import logging.config
import flask
import flask.globals as flask_global
import flask_login
from config import flask as flask_config
from rest.api import dashboard
from rest.api.util import login_decorator
logger = logging.getLogger(__name__)
# app
flask_app = flask.Flask(__name__)
flask_app.config.from_object(flask_config)
# login manager needs be set before blueprint registration
login_manager = flask_login.LoginManager()
login_manager.init_app(flask_app)
flask_app.register_blueprint(dashboard.blueprint)
# setting blueprint specific login view
# login_manager.login_view = "login"
#login_manager.user_loader
def load_user(user_id):
"""
This will be used many times like on using current_user
:param user_id: username
:return: user or none
"""
# http://librelist.com/browser/flask/2012/4/7/current-blueprint/#44814417e8289f5f5bb9683d416ee1ee
blueprint = flask_global.current_app.blueprints[request.blueprint]
if hasattr(blueprint, load_user):
return blueprint.load_user(user_id)
# https://flask-login.readthedocs.org/en/latest/#how-it-works
return None
Here is my blueprint with its own handling of login:
from __future__ import absolute_import
import flask
import flask_login
from flask import Blueprint
from core.models.profile import Agent
from core.utils import thread_local
from rest.api.util import login_decorator
blueprint = Blueprint('human', __name__, url_prefix='/human')
def load_user(user_id):
"""
This will be used many times like on using current_user
:param user_id: username
:return: user or none
"""
agent = None
try:
agent = Agent.objects.get(username=user_id)
except:
# https://flask-login.readthedocs.org/en/latest/#how-it-works
pass
return agent
#blueprint.record_once
def on_load(state):
"""
http://stackoverflow.com/a/20172064/742173
:param state: state
"""
blueprint.load_user = load_user
state.app.login_manager.blueprint_login_views[blueprint.name] = 'human.login'
#blueprint.route('/login', methods=['POST'])
#login_decorator.login_not_required
def login():
username = flask.request.args.get('username')
password = flask.request.args.get('password')
try:
agent = Agent.objects.get(username=username)
except:
return 'Invalid username'
if not agent.check_password(password):
return 'Invalid password'
flask_login.login_user(agent)
return 'Valid login'
#blueprint.route("/logout")
def logout():
flask_login.logout_user()
return 'Logout done'
#blueprint.before_request
def before_request():
agent = flask_login.current_user
# https://flask-login.readthedocs.org/en/latest/#anonymous-users
is_logged_in = agent.get_id() is not None
login_not_required = getattr(flask.current_app.view_functions[flask.request.endpoint], 'login_not_required', False)
is_static_resource_call = flask.request.endpoint.startswith('static/')
if is_static_resource_call or is_logged_in or login_not_required:
if is_logged_in:
thread_local.set_current_brand_id(agent.brand_id)
else:
flask.abort(401)
# if we want to redirect to some page then we can use this. The appropriate login_view should be set
# return flask.current_app.login_manager.unauthorized()
Hope it helps.
In case anyone still faces this challenge due to the documentation not being so clear, here is a solution
In your case, you need to place the login manager declaration in the same file as the flask app instance. This is commonly an __init__.py file with the app = Flask(__name__).
At the top, import LoginManager class
from flask_login import LoginManager
Then tie it to the app instance.
login_manager = LoginManager()
login_manager.init_app(app)
(This was not asked but just incase someone needs it) Lets say you have admins and normal users and you are authenticating from different tables:
#login_manager.user_loader
def load_user(user_id):
x = Users.query.get(str(user_id))
if x == None:
x = Admins.query.get(str(user_id))
return x
Finally after importing blueprints you can define the login views for each in a dictionary
login_manager.blueprint_login_views = {
'admin': '/admin/login',
'site': '/login',
}
Since you tied the login manager to the flask app instance, there is no need of importing it into any blueprint
The documentation is unclear and I will put down what I come up with after spending some time researching.
You only need to define login_manager once in the flask app.py and init_app.
Then at each blueprint, add from flask_login import login_required at top and use the #login_required as usual. Turns out it can be used without stating the login_manager.
Code example (app with single blueprint)
app.py
import flask
from flask import redirect, url_for
import flask_login
from blueprint.secret import secret_bp
from model.user import User
login_manager = flask_login.LoginManager()
app = flask.Flask(__name__)
app.register_blueprint(secret_bp, url_prefix="/secret")
login_manager.init_app(app)
#login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
#app.route('/login', methods=['GET', 'POST'])
def login():
if flask.request.method == "GET":
return "<form action='/login' method='POST'><input type='text' name='user'><button type='submit'>Submit</button></form>"
user = flask.request.form.get('user')
if user == "user":
# Login and validate the user.
# user should be an instance of your `User` class
flask_login.login_user(user)
flask.flash('Logged in successfully.')
return flask.redirect(next or flask.url_for('index'))
return flask.redirect(flask.url_for('login'))
#app.route('/admin')
def admin():
return "Admin page"
#login_manager.unauthorized_handler
def unauthorized():
# do stuff
return redirect(url_for('login'))
secret.py
from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound
from flask_login import login_required
secret_bp = Blueprint('secret', __name__,
template_folder='templates')
#secret_bp.route('/noneed')
def no_need():
return "You can see this without login."
#secret_bp.route('/needlogin')
#login_required
def show():
return "can't see this if not login"
As expected, /secret/noneed can be access without login and /secret/needlogin will redirect you with the function stated with #unauthorized_handler.

Categories

Resources