Django Passwordless Authentication: A Comprehensive Guide with Code Examples
Tags: django, python
Published: 2024-07-03 11:45:33

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](https://www.nilebits.com/blog/) where we cover various topics on web development and cybersecurity.

References

[Django Documentation](https://docs.djangoproject.com/)