In the ever-evolving landscape of web security, passwordless authentication has emerged as a powerful alternative to traditional password-based systems. This approach not only enhances security but also significantly improves user experience. In this comprehensive guide, we’ll delve deep into implementing passwordless authentication in Django, focusing on the magic link method.

Why choose passwordless authentication?

Before we dive into the implementation details, let’s explore the compelling reasons to adopt passwordless authentication:

  1. Enhanced security: By eliminating passwords, we remove vulnerabilities associated with password storage and transmission. This approach mitigates risks such as password reuse, brute force attacks, and phishing attempts.
  2. Improved user experience: Users no longer need to remember complex passwords or go through the frustration of password resets. The login process becomes seamless and user-friendly.
  3. Reduced password management: For both users and administrators, the burden of password management is significantly reduced. No more dealing with forgotten passwords or enforcing regular password changes.
  4. Simplified onboarding: New users can quickly sign up and access your application without the need to create and remember yet another password.

Django for passwordless auth

Let’s walk through the process of implementing passwordless authentication in Django using magic links. We’ll cover every step in detail, from project setup to final testing.

Prerequisites

Before we begin, ensure you have the following:

  • Python 3.6 or higher
  • Django 4.0 or higher
  • An email service configured for sending authentication links

Project setup

1. Create a new Django project and application:

django-admin startproject passwordless_auth
cd passwordless_auth
django-admin startapp authentication

2. Add the authentication app to your project’s INSTALLED_APPS in settings.py:


INSTALLED_APPS = [
    ...
    'authentication',
]

Configuring email backend

For passwordless authentication to work, we need to set up an email backend. In development, you can use Django’s console email backend, which prints emails to the console. For production, you’ll need to configure an actual email service provider.

Add the following to your settings.py:


# 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 = '[email protected]'
# EMAIL_HOST_PASSWORD = 'your-email-password'

Creating the User model

For this implementation, we’ll use Django’s default user model. However, if your application requires additional fields, you may want to create a custom user model. For now, we’ll extend the default user model with a profile model to store the magic token.

Create a new file authentication/models.py:


from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone
from datetime import timedelta

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 __str__(self):
        return self.user.username

    def is_token_expired(self):
        return self.token_created_at < timezone.now() - timedelta(minutes=15)

This profile model extends the user model with fields for the magic token and its creation time. We’ve also added a method to check if the token has expired.

Implementing the authentication views

Now, let’s create the views to handle the authentication process. We’ll need two main views:

  1. A view to generate and send the magic link
  2. A view to verify the magic link and authenticate the user

Create a new file authentication/views.py:


from django.contrib.auth import get_user_model, login
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.token_created_at = timezone.now()
            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, '[email protected]', [email])
            return HttpResponse('A magic link has been sent to your email.')
    return render(request, 'authentication/send_magic_link.html')

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 and not user.profile.is_token_expired():
        user.profile.magic_token = None
        user.profile.save()
        login(request, user)
        return redirect('home')
    else:
        return HttpResponse('Invalid or expired link')

These views handle the core functionality of our passwordless authentication system. The send_magic_link view generates a unique token, associates it with the user, and sends an email containing the magic link. The verify_magic_link view checks the validity of the token and authenticates the user if the token is valid and not expired.

Creating templates

We need to create two HTML templates:

  1. A form for users to request a magic link
  2. The email template containing the magic link

Create a new directory authentication/templates/authentication/ and add the following files:

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>
    <p>This link will expire in 15 minutes.</p>
</body>
</html>

Setting up URLs

Now, let’s set up the URLs for our authentication views. Create a new file 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/', verify_magic_link, name='verify_magic_link'),
]

Include these URLs in your main project’s 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 configuration steps

To complete the setup, we need to perform a few more steps:

1. Apply database migrations:


python manage.py makemigrations
python manage.py migrate

2. Create a superuser for admin access:


python manage.py createsuperuser

3. Ensure you have a home view and URL pattern set up, as our verify_magic_link view redirects to it upon successful authentication.

Testing the implementation

Now that we have our passwordless authentication system set up, let’s test it:

1. Start the development server:


python manage.py runserver

2. Open your browser and navigate to http://localhost:8000/auth/send-magic-link/.
3. Enter an email address associated with a user account in your database.
4. Check the console output (if using the console email backend) or the user’s email inbox for the magic link.
5. Click the magic link to log in. You should be redirected to the home page upon successful authentication.

Security considerations

While passwordless authentication offers numerous security benefits, there are still some considerations to keep in mind:

  1. Token expiration: We’ve implemented a 15-minute expiration for magic tokens. Adjust this duration based on your security requirements.
  2. Secure communication: Ensure that all communication, especially the transmission of magic links, occurs over HTTPS.
  3. Rate limiting: Implement rate limiting on the magic link request endpoint to prevent abuse.
  4. Email security: Be aware that email is not inherently secure. Consider implementing additional verification steps for highly sensitive operations.
  5. Account takeover protection: Implement measures to prevent account takeover attempts, such as notifying users of login attempts from new devices or locations.

Django book

Conclusion

Passwordless authentication using magic links offers a secure and user-friendly alternative to traditional password-based systems. By implementing this approach in Django, we’ve created a robust authentication system that enhances both security and user experience.

This guide has walked you through the entire process of setting up passwordless authentication in Django, from project setup to final testing. By following these steps and considering the security implications, you can confidently implement this modern authentication method in your Django applications.

Remember, while this implementation provides a solid foundation, always consider the specific security needs of your application and consult with security experts when dealing with sensitive user data.

Categorized in:

MLOps, Models deployment, Programming,

Last Update: 21/07/2024