Amr Saafan
Posted on July 3, 2024
Modern security techniques like passwordless authentication improve user experience by doing away with the necessity for conventional passwords. By using this technique, the likelihood of password-related vulnerabilities including reused passwords, brute force assaults, and phishing is decreased. We will go into great length about creating passwordless authentication in Django in this post, along with best practices and comprehensive code samples to guarantee a safe and easy-to-use authentication system.
Introduction to Passwordless Authentication in Django
It is possible for users to log in without a password thanks to passwordless authentication. Alternatively, they use biometrics, one-time passwords (OTPs), or magic links for authentication. This tutorial will concentrate on setting up magic link authentication in Django, which uses the user's email address to deliver a one-of-a-kind, time-limited link for authentication.
Why Choose Passwordless Authentication?
Enhanced Security: Eliminates risks associated with password storage and transmission.
Improved User Experience: Simplifies the login process by removing the need to remember passwords.
Reduced Password Management: Decreases the burden of password resets and management for both users and administrators.
Setting Up Django for Passwordless Authentication
Prerequisites
Before we start, ensure you have the following:
Python installed (version 3.6+)
Django installed (version 3.0+)
An email service setup for sending authentication links
Project Setup
Create a new Django project and application:
django-admin startproject passwordless_auth
cd passwordless_auth
django-admin startapp authentication
Add the authentication app to your project's INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
...
'authentication',
]
Configure Email Backend
Configure your email backend in settings.py to enable sending emails. For development purposes, you can use the console email backend, which prints emails to the console. For production, configure an actual email service provider.
# For development
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# For production (example with SMTP)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.example.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your-email@example.com'
EMAIL_HOST_PASSWORD = 'your-email-password'
Create the User Model
Use Django's default user model or create a custom user model if you need additional fields. For this example, we'll use the default user model.
Create the Authentication View
In the authentication app, create a view to handle the authentication process. This view will generate and send a magic link to the user's email address.
# authentication/views.py
from django.contrib.auth import get_user_model
from django.contrib.sites.shortcuts import get_current_site
from django.shortcuts import render, redirect
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes, force_text
from django.template.loader import render_to_string
from django.core.mail import send_mail
from django.urls import reverse
from django.http import HttpResponse
from django.utils.crypto import get_random_string
User = get_user_model()
def send_magic_link(request):
if request.method == 'POST':
email = request.POST.get('email')
user = User.objects.filter(email=email).first()
if user:
token = get_random_string(32)
user.profile.magic_token = token
user.profile.save()
current_site = get_current_site(request)
mail_subject = 'Your magic login link'
message = render_to_string('authentication/magic_link_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': token,
})
send_mail(mail_subject, message, 'no-reply@example.com', [email])
return HttpResponse('A magic link has been sent to your email.')
return render(request, 'authentication/send_magic_link.html')
Create the Profile Model
Extend the user model with a profile model to store the magic token.
# authentication/models.py
from django.contrib.auth.models import User
from django.db import models
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
magic_token = models.CharField(max_length=64, blank=True, null=True)
def __str__(self):
return self.user.username
Create the Authentication Token Verification View
Create a view to verify the token and authenticate the user.
# authentication/views.py
from django.contrib.auth import login
def verify_magic_link(request, uidb64, token):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and user.profile.magic_token == token:
user.profile.magic_token = None
user.profile.save()
login(request, user)
return redirect('home')
else:
return HttpResponse('Invalid or expired link')
Create Templates
Create templates for sending the magic link and for the email content.
send_magic_link.html
<!DOCTYPE html>
<html>
<head>
<title>Send Magic Link</title>
</head>
<body>
<h1>Send Magic Link</h1>
<form method="post">
{% csrf_token %}
<label for="email">Email:</label>
<input type="email" name="email" id="email" required>
<button type="submit">Send Magic Link</button>
</form>
</body>
</html>
magic_link_email.html
<!DOCTYPE html>
<html>
<head>
<title>Magic Link Login</title>
</head>
<body>
<p>Hi {{ user.username }},</p>
<p>Click the link below to log in:</p>
<a href="http://{{ domain }}{% url 'verify_magic_link' uid=uid token=token %}">Login</a>
</body>
</html>
Create URLs
Define URLs for the authentication views.
# authentication/urls.py
from django.urls import path
from .views import send_magic_link, verify_magic_link
urlpatterns = [
path('send-magic-link/', send_magic_link, name='send_magic_link'),
path('verify-magic-link/<uidb64>/<token>/', verify_magic_link, name='verify_magic_link'),
]
Include the authentication URLs in the main project URLs.
# passwordless_auth/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('auth/', include('authentication.urls')),
]
Final Touches
To complete the setup, make sure you migrate the database to create the necessary tables.
python manage.py makemigrations
python manage.py migrate
Create an admin user to manage the application.
python manage.py createsuperuser
Testing the Implementation
Start the Development Server: Run the Django development server.
python manage.py runserver
Access the Magic Link Form: Open your browser and navigate to http://localhost:8000/auth/send-magic-link/. Enter an email address associated with a user account in your database.
Check the Email: For development, the email will be printed in the console. In a production setup, check the user's email inbox.
Click the Magic Link: Click the link in the email to log in.
Security Considerations
Token Expiration: Implement token expiration to ensure that links are only valid for a limited time.
from datetime import timedelta
from django.utils import timezone
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
magic_token = models.CharField(max_length=64, blank=True, null=True)
token_created_at = models.DateTimeField(auto_now_add=True)
def is_token_expired(self):
return self.token_created_at < timezone.now() - timedelta(minutes=15)
Conclusion
An easier-to-use and safer option to conventional password-based authentication is passwordless authentication. You may improve your application's security and give users an easy way to log in by using Django's magic link authentication feature. You may use this article to help you get started with passwordless authentication in your Django applications by seeing a thorough overview and in-depth code samples.
For more insights on enhancing your Django applications, check out Nile Bits' blog where we cover various topics on web development and cybersecurity.
References
Posted on July 3, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.