How to build a Secure Passwordless Magic Link Login in Django - from scratch Introduction Why passwords are getting obsolete? Passwords present a significant challenges for both developers and users, making the traditional password based authentication less optimal in today's digital landscape. From users perspective: [object Object], [object Object], [object Object] From the Developers Perspective: [object Object], [object Object], [object Object] Why magic links are better? Magic links offer a passwordless authentication method, sending users a unique, time-limited URL via email (or SMS) that grants access when clicked. [object Object], [object Object], [object Object] What you are going to build today? We're going to develop a Django application with session-based authentication and an email-only login. The environment variables will hold the email address for this application. By looking through the list of emails, we will determine whether the user is authorized to log in to the app. If the user's email address is present in the environment variable, we will send the magic link to their email and confirm that. Pre-requisites [object Object], [object Object], [object Object], [object Object] 💻 Project Setup Before we dive into magic link authentication, we need to get a working Django environment up and running. This includes setting up the project, installing dependencies, configuring the environment, and laying out the project structure. This part ensures you have a clean foundation before adding authentication logic. Create a new Django project and app Start by creating a virtual environment for your project. This keeps dependencies isolated and easy to manage. Install Django and the python-dotenv package, which we’ll use to securely manage credentials: Create your Django project and your core app. For this example, we’ll use the names silverback for the project and dashboard for the app: Project Directory Structure Add the new app to your project by editing my_project/settings.py: Also, confirm that SessionMiddleware is included (it is by default): Create and Configure .env for Allowed Emails Instead of using a database of users, we’ll keep a simple list of allowed email addresses in a .env file. This is especially useful for internal tools and admin dashboards. Create a .env file at the project root: Be sure to add .env to your .gitignore file to prevent it from being committed: Load .env in the settings.py Update my_project/settings.py to load the credentials list from the environment Now you can access settings.CREDENTIALS anywhere in your app and check if an email is allowed to log in. Setup the Templates Directory Then update settings.py to include the path: You’ll place login.html, home.html, and other frontend pages here later. Run intial migrations and test your server Django uses sessions to keep track of logged-in users, so you’ll need the django_session table. Run your migrations: 🔐 The Login flow Login Form Traditional login forms often ask users for both their email and a password. In this implementation, we’re removing passwords altogether, users log in using a secure magic link sent to their email. This significantly improves user experience and sidesteps common security issues associated with password storage and handling. We begin by creating a simple Django form that accepts only an email address. This form replaces the usual username/password fields, streamlining the login process for users. In the corresponding view, we validate that the email exists in our list of allowed users, which we store securely in a .env file. If the email is found, we generate a signed login token using Django’s signing framework and construct a one-time-use login link. This link is emailed to the user, who can then click it to log in. This flow removes the need for accounts or passwords, making it ideal for internal tools, dashboards, or low-risk apps. How Django signs and verifies the Magic Link A crucial part of this system is the ability to verify that the magic login link hasn't been tampered with and that it hasn't expired. For this, we rely on Django’s TimestampSigner . When the user submits their email, we sign it with a timestamp using signer.sign(email). The result is a token that looks like this: This string contains the email address, a base62-encoded timestamp, and a cryptographic signature. The signature is generated using your Django project’s SECRET_KEY , which ensures that the token cannot be forged or tampered with by anyone who doesn’t have access to your key. Later, when the user clicks the magic link and lands on the /verify/ route, the application attempts to validate the token using signer.unsign(token, max_age=300). This process checks whether the token has been altered and whether it has expired, in this case, five minutes after it was generated. If either of those checks fails, the login attempt is rejected. If it passes, the system trusts the email embedded in the token and