This is the continuation of Fingerprint-based authentication and Authorization in Python(Django) web applications.
The full code for this article can be accessed on
Sirneij
/
django_mfa2_example
A simple fingerprint-based authentication and authorization application using django-mfa2
django_mfa2_example
Fingerprint-based authentication and authorization system in Python (Django). This can be integrated with e-voting systems and other applications that should be very secure.
A walk-through of this repository can be found on dev.to in this tutorial-like article Fingerprint-based authentication and authorization in Python(Django) web applications.
This example application uses Django-mfa2 to implement a password-less fingerprint-based authentication and authorization system. It's live and can be accessed here.
Run locally
- clone this report:
git clone https://github.com/Sirneij/django_mfa2_example.git
- create and activate virtual environment (I used
pipenv
but you can stick with venv
, virtualenv
or poetry
):
pipenv shell
pipenv install
- makemigrations and migrate:
python manage.py makemigrations
python manage.py migrate
- optionally, createsuperuser:
python manage.py createsuperuser
and is live here. In the live version, your username must start with CPE
.
Sirneij / django_mfa2_example
A simple fingerprint-based authentication and authorization application using django-mfa2
django_mfa2_example
Fingerprint-based authentication and authorization system in Python (Django). This can be integrated with e-voting systems and other applications that should be very secure.
A walk-through of this repository can be found on dev.to in this tutorial-like article Fingerprint-based authentication and authorization in Python(Django) web applications. This example application uses Django-mfa2 to implement a password-less fingerprint-based authentication and authorization system. It's live and can be accessed here.
Run locally
- clone this report:
git clone https://github.com/Sirneij/django_mfa2_example.git
- create and activate virtual environment (I used
pipenv
but you can stick withvenv
,virtualenv
orpoetry
):pipenv shell pipenv install
- makemigrations and migrate:
python manage.py makemigrations python manage.py migrate
- optionally, createsuperuser:
python manage.py createsuperuser
It is time to get into the meat of the article. Let's add some codes to our accounts/views.py
file to handle registration and logging in. For registration, add this code snippet:
def register(request):
if request.method == "POST":
error = ''
username = request.POST.get('username').replace('/', '')
display_name = request.POST.get('display-name')
if not utils.validate_username(username):
error = 'Invalid matriculation number'
return render(request, 'register.html', context = {'page_title': "Register", 'error': error})
if not utils.validate_display_name(display_name):
error = 'Invalid display name'
return render(request, 'register.html', context = {'page_title': "Register", 'error': error})
if User.objects.filter(username=username).exists():
error = 'Student already exists.'
return render(request, 'register.html', context = {'page_title': "Register", 'error': error})
else:
u = User.objects.create(first_name = display_name, password='none', is_superuser=False, username=username, last_name='', display_name=display_name, email='none', is_staff=False, is_active=True,date_joined=timezone.now())
u.backend = 'django.contrib.auth.backends.ModelBackend'
auth.login(request,u)
return redirect(reverse('start_fido2'))
else:
return render(request, 'register.html', context = {'page_title': "Register"})
This is a function-based view in django. It checks to ensure that the incoming request is a POST
and then gets the username
and display_name
from the registration form found in accounts/templates/register.html
. Some of the snippets in this .html
file is:
...
<form method="POST">
{% csrf_token %}
<img
class="mb-4"
src="{% static 'images/logo.png' %}"
alt=""
width="72"
height="57"
/>
<h1 class="h3 mb-3 fw-normal">Register</h1>
{% if error %}
<p class="mb-3 fw-normal text-danger">{{error}}</p>
{% endif %}
<div class="form-floating mb-2">
<input
type="text"
name="username"
id="username"
class="form-control"
placeholder="username"
/>
<label for="username">Username</label>
</div>
<div class="form-floating mb-2">
<input
type="text"
name="display-name"
id="display-name"
class="form-control"
placeholder="display-name"
/>
<label for="display-name">Display name</label>
</div>
<input
type="submit"
value="Register"
class="w-100 btn btn-lg btn-primary"
/>
<p class="mt-5 mb-3 text-muted">© 2021</p>
</form>
...
It is just a basic form with two main input fields and one submit button. These lines:
username = request.POST.get('username').replace('/', '')
display_name = request.POST.get('display-name')
get the value of the field with username
and display_name
as name attribute respectively. The .replace('/', '')
at the end of the username replaces any /
with nothing. Which means if you type in CPE/34/3435
, username
will have CPE343435
without the slashes.
Then, it checks the collected values against some cleaning functions written in accounts/utils.py
. Some of the snippets there is:
def validate_username(username):
if not isinstance(username, six.string_types):
return False
if len(username) > USERNAME_MAX_LENGTH:
return False
if not username.isalnum():
return False
if not username.lower().startswith("cpe"):
return False
return True
def validate_display_name(display_name):
if not isinstance(display_name, six.string_types):
return False
if len(display_name) > DISPLAY_NAME_MAX_LENGTH:
return False
if not display_name.replace(' ', '').isalnum():
return False
return True
Note that these steps aside:
if User.objects.filter(username=username).exists():
error = 'Student already exists.'
return render(request, 'register.html', context = {'page_title': "Register", 'error': error})
ain't that necessary. They are just domain-specific for the application I used it for.
The rest lines should be familiar. What really brings up fingerprint
is this line:
return redirect(reverse('start_fido2'))
It redirects the user, having passsed all the conditions stated, to a function in django-mfa
that incepts the authentication process. Notice that the user was logged in before redirecting. This is to ensure that the generated public key is linked to a particular user. If you don't want this or if you want users without any authenticator to be removed from the database and then logged out, there is a hack I used and will be shared in a later part of this series.
That is it about the registration. Now to authentication. Add this snippet to the accounts/views.py
file:
def login(request):
if request.method == "POST":
username = request.POST.get('username').replace('/', '')
user = User.objects.filter(username=username).first()
err=""
if user is not None:
if user.is_active:
if "mfa" in settings.INSTALLED_APPS:
from mfa.helpers import has_mfa
res = has_mfa(request,username=username)
if res: return res
return login_user_in(request, username)
else:
err="This student is NOT activated yet."
else:
err="No student with such matriculation number exists."
return render(request, 'login.html', {"err":err})
else:
return render(request, 'login.html')
This should be familiar. The real usage of django-mfa2
comes in these lines:
...
if "mfa" in settings.INSTALLED_APPS:
from mfa.helpers import has_mfa
res = has_mfa(request,username=username)
if res: return res
return login_user_in(request, username)
...
It first checks to ensure mfa
is installed and then verifies that the user coming in has some identity with it. Thence, the user is forwarded to login_user_in(request, username)
with the following code snippet:
def login_user_in(request, username):
user=User.objects.get(username=username)
user.backend='django.contrib.auth.backends.ModelBackend'
auth.login(request, user)
if "next" in request.POST:
return redirect(request.POST.get("next"))
else:
return redirect(reverse('accounts:index'))
to authenticate the user. This function serves as the callback which was icluded in part one of this series in our settings.py
file:
...
MFA_LOGIN_CALLBACK="accounts.views.login_user_in" # A function that should be called by username to login the user in session
...
We are almost done! Include these views in our accounts/urls.py
file:
from django.urls import path
from . import views
from django.contrib.auth import views as auth_views
app_name = 'accounts'
urlpatterns = [
path('', views.index, name='index'),
path('login/', views.login, name='login'),
path('register/', views.register, name='register'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]
Note that index
is included in the complete code on github. You can also find all the CSS
, HTML
and JavaScript
there.
To use django-mfa2
well, your template
should have a file named mfa_auth_base.html
with the contents below:
{% extends "base.html" %}
This just extends your base.html
file. For all mfa
views to have the feel and look of your site, ensure you include the file above and on the head part of your base.html
include:
<head>
...
{% block head %} {% endblock %} {% block css %} {% endblock css %}
...
</head>
Now our app is up and running! Test it out.
Please note that webauthn
and fido2
only works via https
protocol. Using http
won't work as expected.
The complete code is on
Sirneij / django_mfa2_example
A simple fingerprint-based authentication and authorization application using django-mfa2
django_mfa2_example
Fingerprint-based authentication and authorization system in Python (Django). This can be integrated with e-voting systems and other applications that should be very secure.
A walk-through of this repository can be found on dev.to in this tutorial-like article Fingerprint-based authentication and authorization in Python(Django) web applications. This example application uses Django-mfa2 to implement a password-less fingerprint-based authentication and authorization system. It's live and can be accessed here.
Run locally
- clone this report:
git clone https://github.com/Sirneij/django_mfa2_example.git
- create and activate virtual environment (I used
pipenv
but you can stick withvenv
,virtualenv
orpoetry
):pipenv shell pipenv install
- makemigrations and migrate:
python manage.py makemigrations python manage.py migrate
- optionally, createsuperuser:
python manage.py createsuperuser
.
See ya in the next part where we tinker with the django-mfa
source code a bit.
Outro
Enjoyed this article? I'm a Software Engineer and Technical Writer actively seeking new opportunities, particularly in areas related to web security, finance, healthcare, and education. If you think my expertise aligns with your team's needs, let's chat! You can find me on LinkedIn and Twitter.
If you found this article valuable, consider sharing it with your network to help spread the knowledge!
Top comments (0)