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
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
const AuthVerify = require('auth-verify')
const auth = new AuthVerify({
mlSecret: 'super_secret_key',
mlExpiry: '5m',
appUrl: 'http://localhost:3000',
storeTokens: 'memory' // in production 'redis'
})
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'
});
Custom SMTP Example
auth.magic.sender({
host: 'smtp.mailgun.org',
port: 587,
secure: false,
sender: 'noreply@yourdomain.com',
pass: 'your_smtp_password'
});
API services Example
auth.magic.sender({
service: 'api',
apiService: 'resend', // 'mailgun', 'sendgrid'
sender: 'noreply@yourdomain.com',
apiKey: 'YOUR_API_KEY'
})
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>`
});
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 });
}
});
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'));
Top comments (0)