Techtales.

How to Implement Magic Link Login in NextJS
TW
Tech Wizard✨Author
Mar 8, 2026
7 min read

How to Implement Magic Link Login in NextJS

00

Passwordless login is becoming the most ideal way for users to log in, as it ensures users have valid email addresses and enhances security, as users do not have to keep guessing their passwords. Unlike OTP login, which sends a code to the user's phone number or email, magic link login is relatively simple to implement and can also be free.

Here is how magic link authentication works:

  1. A user visits our app, enters their email address, and clicks the Send Magic Link button.
  2. They click the link sent to their email, and Magic authenticates them.
  3. The app stores their metadata and redirects them to the dashboard page.
  4. The user is able to view their account.

In this blog, I will discuss my own custom strategy for MagicLink login that is currently used on this website. This is by no means the best practice since it is safer to use auth libraries like Better Auth.

Prerequisites

  • A Next.js 14+ project (App Router)
  • A database (examples use PostgreSQL with a query helper, but adapt as needed)
  • An SMTP provider (Gmail, Resend, Postmark, etc.)

Step 1: Create the Verification Tokens Table

First, we need a table to store our short-lived login tokens. Run the following migration against your database:

The expires_at column is critical — tokens should be short-lived (15–30 minutes is typical) so that a leaked or intercepted link can't be reused later. Although we can just create a normal token with JWT, using a database to store the values has an advantage of revocability.

If a user reports a suspicious login or you suspect a token was compromised, you can delete it from the database, and it's instantly dead. With a JWT, once it's signed and sent, you cannot invalidate it until it naturally expires — you'd have to build a separate token blocklist anyway, which brings you back to needing a database.

Step 2: Set Up Nodemailer

The second step is to install a mailing service to help send the magic links to user emails. Although there are various options like Resend, I prefer using Nodemailer for its simplicity and also since it is free. Run these commands to install Nodemailer.

After installing Nodemailer, you will need to save environment variables, including your email account. You can create app passwords with your Gmail account that will allow Nodemailer to interact with your email and send emails on your behalf. One downside of this is that your mail might go to trash or spam unless you use the Google OAuth platform.

Add your SMTP credentials to .env.local:

The next step is to create a mailer function and a a reusable mailer instance at lib/mailer.ts:

Step 3: The sendMagicLinkEmail Function

Now we will need a function that will send the magic link email. You can create your own custom magic link template or use services that allow you to create email templates. The service that I usually use for this is Mailtrap, as it allows me to create and send custom email templates. 

The sendMagicLinkEmail function takes an email address and a token, then sends the magic link. Create it at lib/auth/sendMagicLinkEmail.ts:

Obviously in a big app, you would have a reusable sendEmail function that would get called with custom instructions. 

Step 4: The requestMagicLink Server Action

For the purpose of simplicity, I will not cover how to create a magic link login form since you can easily find templates and blocks such as using ShadCN UI elements. For this step, let's create a function that gets invoked when users click the Login with magic link button.

This is the function users trigger when they submit the login form. It generates a cryptographically secure token, saves it to the database, and fires the email.

Create it at lib/auth/requestMagicLink.ts:

Security note: We delete any previous tokens for that email before inserting the new one. This ensures only one valid link exists at a time, preventing token accumulation.

Step 5: The Login Form

Wire up a simple login form that calls the requestMagicLink. Create app/login/page.tsx:

Step 6: The Magic Link Route Handler

This is the heart of the flow. When the user clicks the link in their email, this route validates the token, creates a session, and redirects them. Create app/api/auth/magic-link/route.ts:

This is just a simple example of how to do this since I would recommend storing sessions in a Session db table and also using something like Drizzle or Prisma to handle the SQL. In this route, you can also add a rate limiter to prevent DDoS attacks.

Additionally, you might need to clean up the tokens when they expire if they have not been used by the user since this would free up space in the database. The best option is to use a cron job that runs at a specified time and checks for expired tokens in the verification table.

Summary

In this blog, we have covered how to implement magic link login without external auth libraries. Here is a full summary of the auth flow:

Security Checklist

  • Tokens are single-use—deleted immediately after validation
  • Tokens expire—a 15-minute window limits exposure
  • crypto.randomBytes(32) — 256 bits of entropy, not guessable
  • httpOnly cookie—a session cookie is inaccessible to JavaScript
  • One active token per email—old tokens are replaced on each request
  • No user enumeration—the login form shows a success message whether or not the email exists in your database
0
0