Fingerprint-based authentication and Authorization in Python(Django) web applications
Tags: django, webauthn, passwordless, djangomfa
Published: 2021-06-08 06:14:22

Passwords, as great as they have been, have some setbacks, both for users and developers. Applications users tend to always worry about passwords being stolen by phishing tools, or their passwords being leaked online when used on compromised websites. Aside this, they also have to worry about creating and remembering passwords even in contemporary times when password management tools are prevalent. At the flip side of it, Developers have to worry about all the complications of passing passwords through systems and safely storing them in databases. All these, among others, prompted [W3C](https://www.w3.org/) and [FIDO](https://fidoalliance.org/) as well as other tech juggernauts such as Google, Mozilla, Microsoft and Yubico to put forward a new authentication paradigm which allows biometric and other cryptographic means to authenticate users. It is aptly known as [WebAuthn](https://w3c.github.io/webauthn/).

Modern browsers support this awesome technology and there are a couple of its implemetations in various major programming languages and web frameworks. However, this series of tutorials will only focus on implementing it in a [Django](https://www.djangoproject.com/) application using a wonderful django package, [django-mfa2](https://github.com/mkalioby/django-mfa2) which utilizes [python-fido2](https://github.com/Yubico/python-fido2) under the hood.

#### The full code for this article can be accessed on  {% github Sirneij/django_mfa2_example %} and is live [here](https://django-mfa2-example.herokuapp.com/). In the live version, your username must start with `CPE`.

Below are the links to other articles in this series:

  - [Fingerprint-based authentication and Authorization in Python(Django) web applications](https://dev.to/sirneij/fingerprint-based-authentication-and-authorization-in-python-django-web-applications-2c6l) - Introductory part (<cite>This part</cite>)
  - [Fingerprint-based authentication and Authorization in Python(Django) web applications - Second part](https://dev.to/sirneij/fingerprint-based-authentication-and-authorization-in-python-django-web-applications-second-part-57n) - The meaty part
  - [Fingerprint-based authentication and Authorization in Python(Django) web applications - Hacking your way out (Part 3)](https://dev.to/sirneij/fingerprint-based-authentication-and-authorization-in-python-django-web-applications-hacking-your-way-out-part-3-2f3e) - Hacking your way out

Please note that we won't be that particular about the looks of the application. In fact, it was built using bootstrap5's example templates. It is assumed you have atleast gone through [Django tutorial](https://docs.djangoproject.com/en/3.2/intro/tutorial01/) or are familiar with Django.

Now, let's get started. 

Fire up your terminal and navigate or create the directory you will be working on. As for me, I have already created `django-mfa-example` and have activated my virtual environment using [pipenv](https://pypi.org/project/pipenv/).

Install `django` and `django-mfa2`:
```zsh
┌──(sirneij@sirneij)-[~/Documents/Projects/django-mfa2-example]
└─$[sirneij@sirneij django-mfa2-example]$ pipenv install django django-mfa2
```
Create a django project with the name you like. I will stick with `django_mfa_example`.

```zsh
┌──(sirneij@sirneij)-[~/Documents/Projects/django-mfa2-example]
└─$[sirneij@sirneij django-mfa2-example]$ django-admin startproject django_mfa_example .
```
Notice the `.` at the end of the command. This prevents django from creating extra folder to hold the project.

You should now have this file structure:
```zsh
 └──   django_mfa2_example/ 
 │  ├────   asgi.py  
 │  ├────   __init__.py  
 │  ├────   settings.py  
 │  ├────   urls.py  
 │  └────   wsgi.py  
 ├──   manage.py  
 ├──   Pipfile  
 ├──   Pipfile.lock  
 ├──   Procfile 
```
Next, scaffold a django application. I call it `accounts`. Any name is fine, just prefer that.
```zsh
┌──(sirneij@sirneij)-[~/Documents/Projects/django-mfa2-example]
└─$[sirneij@sirneij django-mfa2-example]$ python manage.py startapp accounts
```
Proceed to your project's `INSTALLED_APPS` in `settings.py` file and append `mfa` and `accounts.apps.AccountsConfig` to it as shown below:
```python
# django_mfa_example > settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'mfa', #add this for django-mfa2

    'accounts.apps.AccountsConfig', #your local app
]
```
Configure your `STATICFILES_DIRS`, and `STATIC_ROOT`. Create `static` and `templates` folders at the root of the application and connect them appropriately in your `settings.py` file. 

If you are lost, check the code on [github](https://github.com/Sirneij/django_mfa2_example) to guide you.

In your `accounts` app, create a `urls.py` file and link it to your project's `urls.py`. Also, to use `django-mfa2`, you are required to add it to your project's `urls.py`.

```python
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path, include
import mfa
import mfa.TrustedDevice

urlpatterns = [
    path('admin/', admin.site.urls),
    path('mfa/', include('mfa.urls')), #required to use mfa
    path('devices/add/', mfa.TrustedDevice.add,name="mfa_add_new_trusted_device"), #required if you intend adding some devices
    path("", include('accounts.urls', namespace='accounts')) #include accounts with namespace
]

if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL,
                          document_root=settings.STATIC_ROOT)
```
This line:
```python
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL,
                          document_root=settings.STATIC_ROOT)
```
instructs django to serve the static files during development only or when `DEBUG` is `True`.

Now, let's add some `mfa` specific configurations to our project's `settings.py`

```python
MFA_UNALLOWED_METHODS=()   # Methods that shouldn't be allowed for the user
MFA_LOGIN_CALLBACK="accounts.views.login_user_in"            # A function that should be called by username to login the user in session
MFA_RECHECK=True           # Allow random rechecking of the user
MFA_RECHECK_MIN=10         # Minimum interval in seconds
MFA_RECHECK_MAX=30         # Maximum in seconds
MFA_QUICKLOGIN=True        # Allows quick login for returning users

TOKEN_ISSUER_NAME="django_mfa2_example"      #TOTP Issuer name, this should be your project's name

if DEBUG:
  U2F_APPID="https://localhost"    #URL For U2F
  FIDO_SERVER_ID=u"localhost"      # Server rp id for FIDO2, it is the full domain of your project
else:
  U2F_APPID="https://django-mfa2-example.herokuapp.com"    #URL For U2F
  FIDO_SERVER_ID=u"django-mfa2-example.herokuapp.com"      # Server rp id for FIDO2, it is the full domain of your project

FIDO_SERVER_NAME=u"django_mfa2_example"
MFA_REDIRECT_AFTER_REGISTRATION = 'accounts:index'
MFA_SUCCESS_REGISTRATION_MSG = 'Your keys have successfully been created! You '
```
Since we would like to make the application production-ready, notice the inclusion of these lines:

```python
if DEBUG:
  U2F_APPID="https://localhost"    #URL For U2F
  FIDO_SERVER_ID=u"localhost"      # Server rp id for FIDO2, it is the full domain of your project
else:
  U2F_APPID="https://django-mfa2-example.herokuapp.com"    #URL For U2F
  FIDO_SERVER_ID=u"django-mfa2-example.herokuapp.com"      # Server rp id for FIDO2, it is the full domain of your project
```
If you are going to have a different domain name, edit the `else` block accordingly.

To wrap up this part, let's include some basic settings to our `accounts` application.
First off, populate `accounts/urls.py` with the following:
```python
from django.urls import path

app_name = 'accounts'

urlpatterns = []

```
`app_name = 'accounts'` is necessary since we added `namespace='accounts'` in our project's `urls.py` file. Nothing spectacular aside that.

It's a best practice to override django's default `User` model, therefore open up `accounts/models.py` file and paste the following in:

```python
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    display_name = models.CharField(max_length=32)

```
In the code, we are inheriting from `AbstractUser` and appending `diaplay_name` field to our User table. To know about the reason I used `AbstractUser` and not `AbstractBaseUser`, read this [Vitor Freitas's article](https://simpleisbetterthancomplex.com/tutorial/2018/01/18/how-to-implement-multiple-user-types-with-django.html) and Michael Herman's [Creating a Custom User Model in Django](https://testdriven.io/blog/django-custom-user-model/). 

To make Django aware of this new user model, proceed to your `settings.py` file and add:

```python
AUTH_USER_MODEL = 'accounts.User'
```
If your application name isn't `accounts` and/or your new model isn't `User`, change the line above to:
```python
AUTH_USER_MODEL = 'application_name.new_user_model_name'
```

You can now run migrations to ensure no mistakes have been made.

Note that the complete code for this application is on {% github Sirneij/django_mfa2_example %} in case you run into problems.

The next part can be accessed using [Fingerprint-based authentication and Authorization in Python(Django) web applications - Second part](https://dev.to/sirneij/fingerprint-based-authentication-and-authorization-in-python-django-web-applications-second-part-57n).

## References
  - [Guide to Web Authentication](https://webauthn.guide/) by 
     <cite>Suby Raman</cite> of **Duo Security**
  - [Creating a Custom User Model in Django](https://testdriven.io/blog/django-custom-user-model/) by <cite> Michael Herman </cite>
  - [How to Implement Multiple User Types with Django](https://simpleisbetterthancomplex.com/tutorial/2018/01/18/how-to-implement-multiple-user-types-with-django.html) by <cite> Vitor Freitas </cite>