One of the simpler ways of implementing an authorization system is using the flask-login extension. The project's website contains a detailed and well-written quickstart, a shorter version of which is available in this example.
The extension exposes a set of functions used for:
What it doesn't do and what you have to do on your own:
Below there is a minimal set of steps needed to get everything working.
I would recommend to place all auth related code in a separate module or package, for example auth.py
. That way you can create the necessary classes, objects or custom functions separately.
LoginManager
The extension uses a LoginManager
class which has to be registered on your Flask
application object.
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.init_app(app) # app is a Flask object
As mentioned earlier LoginManager
can for example be a global variable in a separate file or package. Then it can be imported in the file in which the Flask
object is created or in your application factory function and initialized.
A users will normally be loaded from a database. The callback must return an object which represents a user corresponding to the provided ID. It should return None
if the ID is not valid.
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id) # Fetch the user from the database
This can be done directly below creating your LoginManager
.
As mentioned the user_loader
callback has to return an object which represent a user. What does that mean exactly? That object can for example be a wrapper around user objects stored in your database or simply directly a model from your database. That object has to implement the following methods and properties. That means that if the callback returns your database model you need to ensure that the mentioned properties and methods are added to your model.
is_authenticated
This property should return True
if the user is authenticated, i.e. they have provided valid credentials. You will want to ensure that the objects which represent your users returned by the user_loader
callback return True
for that method.
is_active
This property should return True if this is an active user - in addition to being authenticated, they also have activated their account, not been suspended, or any condition your application has for rejecting an account. Inactive accounts may not log in. If you don't have such a mechanism present return True
from this method.
is_anonymous
This property should return True if this is an anonymous user. That means that your user object returned by the user_loader
callback should return True
.
get_id()
This method must return a unicode that uniquely identifies this user, and can be used to load the user from the user_loader
callback. Note that this must be a unicode - if the ID is natively an int or some other type, you will need to convert it to unicode. If the user_loader
callback returns objects from the database this method will most likely return the database ID of this particular user. The same ID should of course cause the user_loader
callback to return the same user later on.
If you want to make things easier for yourself (**it is in fact recommended) you can inherit from UserMixin
in the object returned by the user_loader
callback (presumably a database model). You can see how those methods and properties are implemented by default in this mixin here.
The extension leaves the validation of the username and password entered by the user to you. In fact the extension doesn't care if you use a username and password combo or other mechanism. This is an example for logging users in using username and password.
@app.route('/login', methods=['GET', 'POST'])
def login():
# Here we use a class of some kind to represent and validate our
# client-side form data. For example, WTForms is a library that will
# handle this for us, and we use a custom LoginForm to validate.
form = LoginForm()
if form.validate_on_submit():
# Login and validate the user.
# user should be an instance of your `User` class
login_user(user)
flask.flash('Logged in successfully.')
next = flask.request.args.get('next')
# is_safe_url should check if the url is safe for redirects.
# See http://flask.pocoo.org/snippets/62/ for an example.
if not is_safe_url(next):
return flask.abort(400)
return flask.redirect(next or flask.url_for('index'))
return flask.render_template('login.html', form=form)
In general logging users in is accomplished by calling login_user and passing an instance of an object representing your user mentioned earlier to it. As shown this will usually happen after retrieving the user from the database and validating his credentials, however the user object just magically appears in this example.
The object returned by the user_loader
callback can be accessed in multiple ways.
In templates:
The extension automatically injects it under the name current_user
using a template context processor. To disable that behaviour and use your custom processor set add_context_processor=False
in your LoginManager
constructor.
{% if current_user.is_authenticated %}
Hi {{ current_user.name }}!
{% endif %}
In Python code:
The extension provides a request-bound object called current_user
.
from flask_login import current_user
@app.route("/hello")
def hello():
# Assuming that there is a name property on your user object
# returned by the callback
if current_user.is_authenticated:
return 'Hello %s!' % current_user.name
else:
return 'You are not logged in!'
Limiting access quickly using a decorator
A login_required
decorator can be used to limit access quickly.
from flask_login import login_required
@app.route("/settings")
@login_required
def settings():
pass
Users can be logged out by calling logout_user()
. It appears that it is safe to do so even if the user is not logged in so the @login_required
decorator can most likely be ommited.
@app.route("/logout")
@login_required
def logout():
logout_user()
return redirect(somewhere)
current_user
object?By defult an AnonymousUserMixin is returned:
is_active
and is_authenticated
are False
is_anonymous
is True
get_id()
returns None
To use a different object for anonymous users provide a callable (either a class or factory function) that creates anonymous users to your LoginManager
with:
login_manager.anonymous_user = MyAnonymousUser
This concludes the basic introduction to the extension. To learn more about configuration and additional options it is highly recommended to read the official guide.