WeeShip

Security

Rate 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

  1. Sign up on Upstash
  2. Create a new Redis database
  3. Add the UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN to your .env.local file
  4. Install the packages:terminal1npm install @upstash/redis @upstash/ratelimit
  5. Create a new middleware.js file in the root directory (the same level as the /app folder) and add the following content:/middleware.jsJavascriptTypescript1import { 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}; 27We 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.
  6. 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.js1"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;
  7. 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.