Why Magic Links Fail in Real-World Environments
Passwordless authentication via magic links has long been praised for its simplicity. However, in production, magic links frequently break where developers least expect it. Common failure modes include social media in-app browsers, aggressive anti-phishing bots that pre-fetch and invalidate links before the user clicks, and deep-link routing mismatches between mobile and desktop browsers.
Switching to a 6-digit numeric OTP (One-Time Password) eliminates these vulnerabilities. The user remains on the exact page where they initiated the login flow, checks their email inbox for the numeric code, and enters it without navigating away. Here is how to implement this architecture cleanly using Next.js (App Router) and Supabase.
Step 1: Updating the Supabase Email Template
By default, Supabase sends confirmation links. To switch to numeric codes, navigate to Authentication → Email Templates → Magic Link in the Supabase dashboard and replace the link markup with the {{ .Token }} variable.
Ensure that {{ .ConfirmationURL }} is entirely removed from the template. Leaving it in place causes confusion by providing two different authentication paths. In addition, configure a dedicated SMTP service (such as Resend, Postmark, or AWS SES), as the default Supabase mailer is strictly intended for local testing and rate-limits quickly.
Step 2: Server-Side Client and Cookie Management
In modern Next.js applications, authentication tokens must be managed securely on the server. Utilizing @supabase/ssr allows you to create a centralized Supabase server client that synchronizes cookie storage between Server Components, Server Actions, and Route Handlers without exposing private keys to the client.
Step 3: Dispatching the Code Securely
When creating the API route to dispatch the one-time code, address two critical security aspects:
- IP-based Rate Limiting: Protect your endpoint from spam attacks and unneeded SMTP consumption by throttling requests per client IP.
- Preventing Account Enumeration: Always return a generic success response regardless of whether the email is present in your database, preventing attackers from harvesting valid accounts.
Calling supabase.auth.signInWithOtp with the argument shouldCreateUser: true generates the token and initiates delivery while creating the user account if it does not yet exist.
Step 4: Verification and Brute-Force Prevention
A 6-digit passcode offers only one million possible combinations. Without strict rate limiting, an automated brute-force attack can guess the active token before it expires. The verification endpoint calling supabase.auth.verifyOtp must apply rate limiting keyed to the email address rather than the IP address, since distributed attackers easily cycle proxy IPs.
A sensible threshold is capping verification at 5 failed attempts per 10 minutes per email. Upon successful validation, the server client persists the user session directly into secure HTTP-only cookies.
Step 5: Session Refreshing via Next.js Middleware
To keep user sessions active and authenticated across requests, implement session validation within middleware.ts. Crucially, rely on supabase.auth.getUser() rather than merely reading the session object from cookies. The getUser() method verifies the authentication state with the Supabase auth server, guaranteeing that revoked or altered credentials cannot access protected routes.
Strategic Takeaway for Web Applications
Transitioning from magic links to numeric OTP authentication drastically improves conversion rates, minimizes user frustration, and removes deep-link dependencies. At ViAcore, our engineering team builds secure, scalable authentication flows that safeguard business data while delivering a friction-free user journey.
Services on this topic
Landing page turnkey
Selling landing page turnkey: offer, structure, design, development, SEO foundation and ad launch. From 10,000 UAH.
from 10,000 UAH
Corporate website
Corporate website turnkey: company presentation, service catalog, CRM, admin panel, SEO structure. From 18,000 UAH.
from 18,000 UAH
Online store
Online store turnkey: product catalog, cart, online payment, CRM and warehouse integrations, SEO structure. From 25,000 UAH.
from 25,000 UAH
Need this for your project?
Development turnkey: analysis → implementation → launch → support. Tell us about your task — we will reply with an estimate.
Relevant articles
Testing REST and GraphQL Contracts Beside Your UI in Cypress
Learn how running consumer API contract checks directly in Cypress prevents regressions, streamlines debugging, and stabilizes web applications.
Modern Static Site Architecture: How to Automate Audits and Accelerate Releases
Discover how the latest advancements in static site tooling help companies publish faster, protect SEO integrity, and streamline multi-page web development.
Beyond the Demo: How to Build Resilient LLM Architecture for High Load
Learn how to architect resilient production AI systems that survive traffic spikes, handle latency gracefully, and protect critical business workflows.
Integrating Local LLMs into Web Apps: An Alternative to Costly Cloud APIs
Learn how self-hosted open-weight AI models allow businesses to reduce API costs, protect sensitive data, and build scalable interactive web solutions.
Black Box Testing: How to Validate Digital Products Through the User's Eyes
Learn what black box testing is, how it protects software investments, and why businesses need end-user quality assurance to maximize conversions.
Securing LLM Integrations: Why Prompt Injection Isn't Just Another SQLi
Discover why database security fixes fail for generative AI and how to design safe LLM integrations using architectural guardrails.