Building a secure admin interface with Flask-Admin and Flask-Security

Implement authentication and authorization with Flask-Security

Adding users and roles to the database

The basic building blocks of authorization and authentication are users and user roles where users represent the people trying to access our application and user roles represent the roles that we assign to those people to determine the parts of our application to which they have access. Therefore, our first step is to add the data models representing our users and user roles to our database.

from flask_security import RoleMixin, UserMixin
from flask_sqlalchemy import SQLAlchemy
...db = SQLAlchemy(secureApp)roles_users_table = db.Table('roles_users',
db.Column('users_id', db.Integer(),
db.ForeignKey('users.id')),
db.Column('roles_id', db.Integer(),
db.ForeignKey('roles.id')))
class Users(db.Model, UserMixin):
id = db.Column(db.Integer(), primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(80))
active = db.Column(db.Boolean())
roles = db.relationship('Roles', secondary=roles_users_table,
backref='user', lazy=True)
class Roles(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
...

Creating users with Flask-Security

To initialize Flask-Security, we need at least two parameters. The first is a reference to our Flask application. The second is a reference to an instance of a user datastore. A datastore is a repository for persistently storing and managing collections of data. As mentioned in the Adding users and roles to the database section above, this exercise implements a PostgreSQL database which will contain the user datastore and which we interact with via the SQLAlchemy interface. Therefore, the final piece to the puzzle is to extract our users and our user roles from the database using this interface. Luckily, Flask-Security provides the SQLAlchemyUserDatastore class to do this.

from flask_security import Security, SQLAlchemyUserDatastore...user_datastore = SQLAlchemyUserDatastore(db, Users, Roles)
security = Security(secureApp, user_datastore)
...
@secureApp.before_first_request
def create_user():
db.session.add(Users(email='admin', password='admin')
db.session.commit()
@secureApp.before_first_request
def create_user():
user_datastore.create_user(email='admin', password='admin')
db.session.commit()
@secureApp.before_first_request
def create_user():
first_user = user_datastore.create_user(email='admin',
password='admin')
user_datastore.toggle_active(first_user)
db.session.commit()

Securing Flask-Admin views

The whole reason that we implemented Flask-Security for this exercise was to ensure that only authenticated users were able to manage the data in our database. In this step, we will implement this by creating a view of the data from our User data model and modifying it with our own access control rules.

from flask_admin.contrib.sqla import ModelView
from flask_security import current_user
...class UserModelView(ModelView):
def is_accessible(self):
return (current_user.is_active and
current_user.is_authenticated)
def _handle_view(self, name):
if not self.is_accessible():
return redirect(url_for('security.login'))
...

Creating a consistent user experience between Flask-Security and Flask-Admin

Everything appears to be running fine, but we notice that when we click ‘Login’ or ‘Register’ we seem to leave our beautiful Flask-Admin experience and enter some horrendous, unstyled markdown page.

mkdir templates/security
touch templates/security/login_user.html
touch templates/security/register_user.html
{% extends 'admin/master.html' %}
jinja2.exceptions.UndefinedError: 'admin_base_template' is undefined
...@security.context_processor
def security_context_processor():
return dict(
admin_base_template = admin.base_template,
admin_view = admin.index_view,
h = admin_helpers,
get_url = url_for
)
...
{% extends 'admin/master.html' %}
{% from "security/_macros.html" import render_field_with_errors, render_field %}
{% block body %}
{{ super() }}
<div class="container">
<div>
<h1>Login</h1>
<div class="well">
<form action="{{ url_for_security('login') }}" method="POST"
name="login_user_form">
{{ login_user_form.hidden_tag() }}
{{ render_field_with_errors(login_user_form.email) }}
{{ render_field_with_errors(login_user_form.password) }}
{{ render_field(login_user_form.next) }}
{{ render_field(login_user_form.submit, class="btn btn-
primary") }}
</form>
<p>
Not yet signed up? Please <a href="{{
url_for('security.register') }}">register for an
account</a>.
</p>
</div>
</div>
</div>
{% endblock %}
{% extends 'admin/master.html' %}
{% from "security/_macros.html" import render_field_with_errors,
render_field %}
{% block body %}
{{ super() }}
<div class="container">
<div>
<h1>Register</h1>
<div class="well">
<form action="{{ url_for_security('register') }}"
method="POST" name="register_user_form">
{{ register_user_form.hidden_tag() }}
{{ render_field_with_errors(register_user_form.email) }}
{{ render_field_with_errors(register_user_form.password) }}
{% if register_user_form.password_confirm %}
{{ render_field_with_errors(register_user_form.password_confirm) }}
{% endif %}
{{ render_field(register_user_form.submit, class="btn btn-primary") }}
</form>
<p>Already signed up? Please
<a href="{{ url_for('security.login') }}">log in</a>.
</p>
</div>
</div>
</div>
{% endblock body %}

About the author

Colin Kraczkowsky recently returned to web development after exploring the craft of product management. Colin’s professional history includes working in both enterprise and start up environments to code web and mobile applications, launch new products, build mockups and prototypes, analyze metrics, and continuously innovate.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Colin Kraczkowsky

Problem solver wielding JavaScript and Solidity as my tools. Scholar of the newly possible.