SecurityRate limiting (Magic Links)

NextAuth does not have a built-in functionality to rate limit magic link emails.

Instead, you can use a service like Upstash to secure your /api/auth/signin/email endpoint (responsible for signing up with magic links).

Setup

  • Sign up on Upstash
  • Create a new Redis database
  • Add the UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN to your .env.local file
  • Install the packages:

    terminal

    1npm install @upstash/redis @upstash/ratelimit
  • Create a new middleware.js file in the root directory (the same level as the /app folder) and add the following content:

    /middleware.js

    1import { NextResponse } from "next/server";
    2import { Ratelimit } from "@upstash/ratelimit";
    3import { Redis } from "@upstash/redis";
    4
    5const redis = new Redis({
    6  url: process.env.UPSTASH_REDIS_REST_URL,
    7  token: process.env.UPSTASH_REDIS_REST_TOKEN,
    8});
    9
    10const ratelimit = new Ratelimit({
    11  redis: redis,
    12  limiter: Ratelimit.slidingWindow(5, "60 s"),
    13});
    14
    15export default async function middleware(request) {
    16  const ip = request.ip ?? "127.0.0.1";
    17  const { success } = await ratelimit.limit(ip);
    18
    19  return success
    20    ? NextResponse.next()
    21    : NextResponse.redirect(new URL("/blocked", request.url));
    22}
    23
    24export const config = {
    25  matcher: ["/api/auth/signin/email"],
    26};
    27
    We are rate limiting the user to 5 requests per minute based on their IP using the sliding window algorithm. You can refer to the Upstash ratelimit SDK documentation for more information on customizing it.
  • Create a new /app/blocked/page.js file. This is the page the user will be redirected to when they hit the rate limit. Add the following content:

    /app/blocked/page.js

    1"use client";
    2
    3import config from "@/config";
    4import { signIn } from "next-auth/react";
    5import React from "react";
    6import Link from "next/link";
    7
    8const Blocked = () => {
    9	return (
    10		<main className="relative bg-neutral text-neutral-content h-screen w-full flex flex-col justify-center gap-8 items-center p-10">
    11			<h1 className="text-xl md:text-2xl font-medium">
    12				Hm, Access Blocked
    13			</h1>
    14			<p>Try again in 1 minute</p>
    15
    16			<div>
    17				<button
    18					onClick={() =>
    19						signIn(undefined, {
    20							callbackUrl: config.auth.callbackUrl,
    21						})
    22					}
    23					className="link"
    24				>
    25					Login
    26				</button>{" "}
    27				or{" "}
    28				<Link className="link" href="/">
    29					Home
    30				</Link>
    31			</div>
    32		</main>
    33	);
    34};
    35
    36export default Blocked;
  • That's it! You have successfully rate limited the Magic Link sign-ins. Now, when a user hits the rate limit, they will be redirected to the /blocked page.