DEV Community

Cover image for How to create Magic Link authentication system for email verification on Node.js (step-by-step)
Jahongir Sobirov
Jahongir Sobirov

Posted on

How to create Magic Link authentication system for email verification on Node.js (step-by-step)

Magic link authentication is becoming one of the most popular ways to verify users without passwords. Instead of asking users to remember passwords, we simply send them a secure login link through email.

What is a Magic Link?

A magic link is a unique URL sent to a user’s email. When the user clicks the link:

  • The server verifies the token
  • The user becomes authenticated
  • No password is required Example:
https://yourapp.com/verify?token=abc123
Enter fullscreen mode Exit fullscreen mode

This approach is:

  • More secure than passwords in many cases
  • Easier for users
  • Great for modern SaaS apps

Basic setup for magic link authentication system

Firstly, you need to install auth-verify:

npm install auth-verify
Enter fullscreen mode Exit fullscreen mode
const AuthVerify = require('auth-verify')
const auth = new AuthVerify({
    mlSecret: 'super_secret_key',
    mlExpiry: '5m',
    appUrl: 'http://localhost:3000',
    storeTokens: 'memory' // in production 'redis'
})
Enter fullscreen mode Exit fullscreen mode

You need also web server like built on Express.js

Configure Magic Link Sender

Before sending links, you must set up your email transport.

Gmail Example

auth.magic.sender({
  service: 'gmail',
  sender: 'yourapp@gmail.com',
  pass: 'your_gmail_app_password'
});
Enter fullscreen mode Exit fullscreen mode

Custom SMTP Example

auth.magic.sender({
  host: 'smtp.mailgun.org',
  port: 587,
  secure: false,
  sender: 'noreply@yourdomain.com',
  pass: 'your_smtp_password'
});
Enter fullscreen mode Exit fullscreen mode

API services Example

auth.magic.sender({
  service: 'api',
  apiService: 'resend', // 'mailgun', 'sendgrid'
  sender:  'noreply@yourdomain.com',
  apiKey: 'YOUR_API_KEY'
})
Enter fullscreen mode Exit fullscreen mode

For "mailgun" you should also add also your domain

domain: "your-domain.com"

Send Magic Link

Send a secure, expiring link to the user’s email:

auth.magic.send('user@example.com', {
  subject: 'Your Secure Login Link ✨',
  html: `<p>Click below to sign in:</p>
         <a href="{{link}}">Login Now</a>`
});
Enter fullscreen mode Exit fullscreen mode

The {{link}} placeholder will automatically be replaced with the generated magic link.

Verify Magic Link

Typically used in your backend /auth/verify route:

app.get('/auth/verify', async (req, res) => {
  const { token } = req.query;
  try {
    const user = await auth.magic.verify(token);
    res.json({ success: true, user });
  } catch (err) {
    res.status(400).json({ success: false, message: err.message });
  }
});
Enter fullscreen mode Exit fullscreen mode

Full working example

const express = require('express');
const bodyParser = require('body-parser');
const AuthVerify = require('auth-verify');

const app = express();
app.use(bodyParser.json());

const auth = new AuthVerify({
  mlSecret: 'supersecretkey',
  appUrl: 'http://localhost:3000',
  storeTokens: 'memory'
});

auth.magic.sender({
  service: 'gmail',
  sender: 'yourapp@gmail.com',
  pass: 'your_app_password'
});

// Send link
app.post('/auth/send', async (req, res) => {
  const { email } = req.body;
  await auth.magic.send(email);
  res.json({ message: 'Magic link sent!' });
});

// Verify link
app.get('/auth/verify', async (req, res) => {
  try {
    const user = await auth.magic.verify(req.query.token);
    res.json({ message: 'Login successful!', user });
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

app.listen(3000, () => console.log('🚀 Server running on port 3000'));
Enter fullscreen mode Exit fullscreen mode

Top comments (0)