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
pipenvbut you can stick withvenv,virtualenvorpoetry):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
pipenvbut you can stick withvenv,virtualenvorpoetry):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)