Authentication
Security is a fundamental part of most applications, and authentication - validating the identity of a user - is where security starts. In this part of the tutorial, we will implement a local authentication scheme in which user credentials are stored in the local database. Other schemes such as OAuth allow you to make use of credentials stored by services such as Facebook and Google to authenticate users.
Configuring authentication
As is the case with many standard application features, there is a handy Flask extension for handling authentication. To get started, you will need to add two packages to your virtual environment. They are
-
Flask-Login: This package handles all of the main authentication functions transparently.
-
email-validator: This robust email address syntax checker is required by Flask-Login.
Following the pattern established with previous extensions, you also need to include the new
functionality into the application by modifying the factory function. Edit app/__init__.py
and add the import statement shown below.
1 |
|
Then, add the following line after initialising the global db
variable, but before the
declaration of the factory function.
1 |
|
Finally, add the following code befor the blueprint registrations.
1 2 3 |
|
Explanation
Line 1: Initialise the login manager
Line 2: Define the default message to show when the user is not authenticated.
Line 3: Define a view to use for the login dialogue.
You will notice that we are maintaining a structure to the factory function which goes:
- General initialisation
- Flask extensions
- Blueprint registrations
- Error message definitions
We will add further sections later on.
Creating the user model
In order to use the Flask-Login functionality, we need to add a model and corresponding database
table to represent the application users. In this case, that is the staff
model. Create the file
app/models/staff.py
and paste in the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Explanation
Lines 1 - 3: Required imports. Werkzeug is a library of WSGI functions used by Flask.
Line 6: Staff class definition - note that it inherits from
UserMixin
as well asdb.Model
.Line 13: Plain text passwords are not stored, only a hash
Line 14: This line defines an attribute as a foreign key
Lines 16 - 25: These line define a property which is not part of the database table. It is not visible directly and only exists so that the password hash can be stored and a password used at login can be verified against the stored hash.
Lines 31 - 33: The
user_loader
callback is used by Flask-Login to reload the user object from the user id stored in the session.
Because our models are all in separate files, we also need to add the following line to
app/models/__init__.py
so that the new model can be found by import statements.
1 |
|
Remember that you need to migrate the database changes. Open a terminal panel in PyCharm and enter the following command.
1 |
|
This will create the migration script. Afterwards, run the upgrade
command as shown below.
1 |
|
Setting up the auth
blueprint
We created a directory branch for the auth
blueprint earlier in the tutorial, and now we will
develop the contents starting with the login form. Many applications allow users to register for
an account, but in our case we need to ensure that only genuine members of staff have access.
We therefore assume that account creation will be done by an administrator. If you are interested
to know how to write a registration form which includes a password verification field, please
refer to Mbithe Nzomo's original tutorial.
We will create the login form in the file app/auth/forms/login.py
using the following code.
1 2 3 4 5 6 7 8 9 |
|
Next, we need views for logging in and out. Create the file app/auth/views/login.py
and paste in
the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Explanation
Lines 14 - 15: Check whether employee exists in the database and whether the password entered matches the password in the database.
Line 16: Record that the user is successfully authenticated in the session.
Line 17: Redirect to the home page after logging in.
Line 21: Use the generic template defined earlier to render the form
Line 25: Use the
login_required
decorator from Flask-Login to check that the user is logged in. If not, executing thelogout()
function will raise a 403 error.
The final step in configuring the auth
blueprint is to register it in the factory function.
First, we need to indicate that the app/auth
directory represents a blueprint. Do this by
adding the following code to the fileapp/auth/__init__.py
.
1 2 3 4 5 |
|
Then open app/__init__.py
and add the following lines after the other blueprint registrations.
1 2 |
|
User data
To test the authentication functions, we need to add a user to the database. Users in the admin role will need to do this on a regular basis, so the best approach would be to create endpoints to handle user management tasks. As we did with subject groups, we need to implement the CRUD operations.
Staff form
The form needed to create or edit a staff record is shown below. Paste the code into the file
app/admin/forms/staff.py
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Points of interest
Lines 3 & 12: A
QuerySelectField
populates an HTMLselect
element from the results of a database query.Lines 13 - 17: The form contains two password fields which must match to pass validation.
List template
The template for listing members of staff is shown below. Paste the code into the file
app/templates/admin/staff.html
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
Staff views
Create the file app/admin/views/staff.py
and paste in the following code to define the staff views.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
|
Points of interest
Line 71: The
subject_group
field on the form is populated by setting it equal to the related subject group object, not thesubject_group_id
property.Lines 32, 53 & *3: Notice that exception handling has been added to all three operations which update the database.
To permit the assignment at line 71, we need to define a relationship between the Staff
and
SubjectGroup
models in the file app/models/subject_groups.py
. Update the contents to match
the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Explanation
Line 10: The relationship is defined in the parent class. The example here defines a property called
staff
which will contain the list of staff members related to the specific subject group. It also defines abackref
- this identifier can be used in the child object to access the related parent. Here, that means that aStaff
object will have asubject_group
property.
Importing the views
To make the new views accessible to the application, the following row needs to be added to the
file app/admin/__init__.py
.
1 |
|
Menu items
The authentication endpoints and the new staff list route will be accessed by items on the
application menu which is defined in the base template, app/templates/base.html
. Find the
div
element with the id main-navbar
. Replace its contents with the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Explanation
Line 16: The menu items will be different depending on whether the user is currently logged in or not. Here we use the
is_authenticated
property of the Flask-Login proxycurrent_user
to determine the current state.Line 17: Change password link
Line 18: Logout link.
Line 20: Login link.
Adding a new user
Restart the application and the new items should appear on the menu. You should see the Data maintenance drop-down and the new Login option. On the Data maintenance menu, choose Staff.
Click New and fill in the required details. Then, try logging in as the new user.
Changing password
We must allow the users to update their passwords - you might even consider forcing them to
change their passwords after a certain period of time. To enable this functionality, we need
to add a route to the auth
blueprint and we need a corresponding form.
Form
Paste the code below into the file app/auth/forms/password.py
.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Route
Open the file app/auth/views/login.py
and adde the route shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
You will also need to add the following import statements at the top of the file.
1 2 3 4 5 |
|
Protecting endpoints from non-authenticated users
When we created the logout()
function, we used a decorator defined by Flask-Login to prevent
unauthenticated users from calling it. We need to apply the same decorator to all other endpoints
that should not be visible unless the user is logged in.
Open app/subject_group/views/subject_group.py
and add the login_required
decorator to all four
endpoints. The code below shows the list_subject_groups
endpoint by way of an example.
1 2 3 4 5 6 7 |
|
You will also need to add the following import statement.
1 |
|
Do the same for the routes defined in app/admin/views/staff.py
.
We can also improve the behaviour of the menu by hiding the items that non-authenticated users
should not see. To do this, we will simply move the conditional statement in the base template
to a different location to take in the Data maintenance drop-down as well as the Change
password and Logout options. Open app/templates/base.html
and find the div
element
with the id main-navbar
. Update its contents to match the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Points of interest
Line 2: The conditional statement now includes the Data maintenance drop-down.
Summary
Authentication is a fundamental feature of most applications. Frameworks like Flask make it easier to implement by providing ready-made extensions like Flask-Login to do most of the work. However, tailoring the functionality for your own application still takes quite a bit of effort.