Django Magic Link Authentication

Learn how magic link authentication work by building one

Django Magic Link Authentication

Table Of Contents

Basic Setup

# prepare the env
virtualenv .venv
source .venv/bin/activate

# install Django
pip install django
django-admin startproject config .

Create a new app to manage our authentication and add it to INSTALLED_APPS.

python startapp accounts

Install and add Redis as a cache backend. We can later use Redis for storing our magic link tokens.

pip install django-redis

Add the following to

    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",

SESSION_ENGINE = "django.contrib.sessions.backends.cache"

LOGIN_REDIRECT_URL = "dashboard"
LOGIN_URL = "home"

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Here we set up Redis to be our cache backend as well as for session cache. LOGIN_REDIRECT_URL and LOGOUT_REDIRECT_URL tell our application to redirect users after login and logout, respectively. LOGIN_URL is where our application would redirect our user if the user goes to a protected route without logging in. Finally, EMAIL_BACKEND is set to the console.

The Views

Let’s add some basic views to accounts/

from django.shortcuts import render
from django.http.request import HttpRequest
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_GET, require_http_methods

@require_http_methods(["GET", "POST"])
def home(request: HttpRequest):
    return render(request, "magic.html")

def dashboard(request: HttpRequest):
    return render(request, "dashboard.html")

Here the home view handles the login form as well as sending the email. We will implement this feature in a later section.

The Templates

Create the corresponding templates in the templates folder in the root directory. Make sure you set the templates directory in the file as well.

<!-- templates/base.html -->

    {% block content %}{% endblock content %}
<!-- templates/magic.html -->

{% extends 'base.html' %} {% block content %}
<br />
<form method="post">
  {% csrf_token %}
  <input type="email" name="email" required />
  <input type="submit" value="Send Login Instructions" />
{% endblock content %}
<! -- templates/dashboard.html -->

{% extends 'base.html' %} {% block content %}
<br />
<h3>Hello {{}}</h3>
{% endblock content %}

Here are the steps to send the magic link

Update the home view to send the magic link

from .forms import MagicLinkForm
from django.core.mail import send_mail
from django.core.cache import cache
import secrets

@require_http_methods(["GET", "POST"])
def home(request: HttpRequest):
    if request.POST:
        form = MagicLinkForm(request.POST)
        if form.is_valid():
            email = form.cleaned_data["email"]
            token = secrets.token_urlsafe(nbytes=32)
            link = f"http://localhost:8000/magic-link/{token}"
            cache.set(token, email, timeout=10 * 60)
                subject="Magic Link",
                message=f"You link: {link}",
                from_email="[email protected]",
    return render(request, "magic.html")

The MagicLinkForm comes from accounts/

# accounts/

from django import forms

class MagicLinkForm(forms.Form):
    email = forms.EmailField()

Navigate to http://localhost:8000 and use a random email to submit the form. Check your console; you should’ve received an email.

Email sent to console

Verify The Token

Now that the magic link is set up, we need to verify the token and authenticate the user.

# accounts/

from django.http.response import HttpResponseBadRequest
from django.contrib.auth.models import User
from django.contrib.auth import login

def autheticate_via_magic_link(request: HttpRequest, token: str):
    email = cache.get(token)
    if email is None:
        return HttpResponseBadRequest(content="Magic Link invalid/expired")
    user, _ = User.objects.get_or_create(email=email)
    login(request, user)
    return redirect("/dashboard")

Add the URL for the view to accounts/

# accounts/

urlpatterns = [
    path("", home, name="home"),
    path("dashboard", dashboard, name="dashboard"),
    path("magic-link/<str:token>", autheticate_via_magic_link, name="magic_link"),

Check the new URL using a random token or an expired one.

Wrong Token


Django Magic Link Demo

The objective of this experiment is to demonstrate how magic links work. However, for your production Django application, I would recommend not using the same approach. This is because the production environment requires a lot of security, and django-sesame is recommended.

Amal Shaji © 2021.