https://github.com/schero94/strapi-plugin-magic-link-v5
Secure passwordless authentication for Strapi using email-based magic links. No passwords required.
https://github.com/schero94/strapi-plugin-magic-link-v5
authentication magiclink passwordless passwordless-auth passwordless-authentication passwordlessauthentication plugin plugin-strapi strapi strapi-plugin
Last synced: 30 days ago
JSON representation
Secure passwordless authentication for Strapi using email-based magic links. No passwords required.
- Host: GitHub
- URL: https://github.com/schero94/strapi-plugin-magic-link-v5
- Owner: Schero94
- License: mit
- Created: 2025-03-23T14:49:01.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2026-04-26T19:44:29.000Z (about 1 month ago)
- Last Synced: 2026-04-26T21:28:52.176Z (about 1 month ago)
- Topics: authentication, magiclink, passwordless, passwordless-auth, passwordless-authentication, passwordlessauthentication, plugin, plugin-strapi, strapi, strapi-plugin
- Language: JavaScript
- Homepage:
- Size: 5.8 MB
- Stars: 12
- Watchers: 1
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# π Magic Link - Passwordless Authentication for Strapi v5
**The most advanced passwordless authentication plugin for Strapi v5**
Secure, modern, and user-friendly authentication using email-based magic links, OTP codes, and TOTP authenticators. Built for production with enterprise-grade security features.
[](LICENSE)
[](https://www.npmjs.com/package/strapi-plugin-magic-link-v5)
[](https://strapi.io)
## Why Magic Link?
- **Zero Setup Time** - Install, configure SMTP, and start using.
- **Production Ready** - Battle-tested with rate limiting, IP bans, and session management.
- **Multi-Factor Authentication** - Email OTP + TOTP Authenticator support, included.
- **WhatsApp Integration** - Send magic links via WhatsApp messenger.
- **Beautiful UI** - Modern, responsive admin interface localized in 5 languages.
- **Free & Open** - MIT license, no paywall, every feature available out of the box.
## π Quick Links
- [β¨ Features](#-features) - Authentication modes, security, license tiers
- [π Installation](#-installation) - Get started in minutes
- [π― Quick Start](#-quick-start) - License activation & configuration
- [π» Frontend Examples](#-frontend-implementation) - Magic Link, Email OTP, TOTP flows
- [π‘ API Endpoints](#-api-endpoints) - Complete API reference
- [π Plugin Compatibility](#-plugin-compatibility) - Migration from other plugins
- [βοΈ Configuration](#-configuration) - All settings explained
- [π Troubleshooting](#-troubleshooting) - Common issues & solutions
- [π Changelog](#-changelog) - Version history & updates
## π Supported Languages
The admin interface is available in **5 languages** for international accessibility:
- π¬π§ **English** - Global standard
- π©πͺ **Deutsch** - German (DACH region)
- π«π· **FranΓ§ais** - French (Strapi's home & community)
- πͺπΈ **EspaΓ±ol** - Spanish (Spain & Latin America)
- π΅πΉ **PortuguΓͺs** - Portuguese (Brazil & Portugal)
Users can switch languages in **Settings β Magic Link β Interface Language**.
---
## π License
MIT-licensed. Free for personal and commercial use. See [LICENSE](./LICENSE).
### Optional license-key activation
The admin panel offers a "License" tab where you can register an optional
license key. This is purely cosmetic β it does **not** unlock any
feature, and the plugin is fully usable without it. Registering a key
just lets the project track installs and helps us prioritise issues.
You can ignore the License tab entirely; nothing will be gated.
---
## β¨ Features
### Authentication Modes
All modes are available in every install β pick whichever fits your security profile:
| Mode | Description |
|------|-------------|
| **Magic Link Only** | One-click email login β fast & user-friendly |
| **Magic Link via WhatsApp** | Send magic links via the WhatsApp messenger |
| **Magic Link + Email OTP** | 6-digit code sent after the magic link click |
| **Magic Link + TOTP (MFA)** | Authenticator app (Google Authenticator, Authy, β¦) |
| **TOTP-Only Login** | Direct login with email + TOTP code |
### π‘οΈ Security Features
- **Rate Limiting** β 5 requests per 15 minutes (configurable)
- **IP Banning** β Block suspicious addresses with one click
- **Session Management** β Monitor and revoke active JWT sessions
- **Login Tracking** β Store IP addresses and user agents
- **Token Expiration** β Configurable validity periods
- **Brute Force Protection** β Login attempt limiting
- **Security Score** β Real-time configuration assessment
### π¨ Admin Interface
- **Modern Dashboard** - Beautiful token statistics and charts
- **Mobile Optimized** - Perfect icon centering on all screen sizes
- **German Translations** - Fully localized UI (5 languages total)
- **License Management** - Visual tier display and feature unlocking
- **Professional Token Management** - Create, extend, block, delete
- **Search & Filter** - Find anything in seconds
- **Bulk Operations** - Manage multiple items at once
### Customization
- **Email Templates** - HTML & plain text with variables
- **WhatsApp Templates** - Customizable WhatsApp message content
- **Email Designer 5** - Visual email builder integration
- **Flexible Configuration** - All settings in admin panel
- **Frontend URL** - Configure custom frontend login URLs
- **Custom Callbacks** - Post-login redirect URLs
- **Auto User Creation** - Optional on first login
- **Token Reusability** - One-time or multi-use tokens
---
## πΈ Screenshots
### Token Management Dashboard
Professional interface for managing magic link tokens with real-time statistics.

### Create New Token
Simple modal to create tokens with custom TTL and context data.

### JWT Session Management
Monitor and manage all active JWT sessions across your application.

### IP Ban Management
Security feature to block suspicious IP addresses.

### Settings Interface
Comprehensive settings panel with modern UI.

### General Settings
Configure core functionality and authentication options.

---
## WhatsApp Integration (NEW!)
Magic Link now supports sending authentication links via **WhatsApp** - completely FREE!
### Why WhatsApp?
- **Higher Open Rates** - 98% open rate vs 20% for email
- **Instant Delivery** - No spam folders, no delays
- **User Preference** - Many users prefer messaging over email
- **FREE** - No additional costs, uses your own WhatsApp number
### Quick Setup
1. Navigate to **Admin Panel -> Magic Link -> WhatsApp**
2. Scan the QR code with your WhatsApp mobile app
3. Done! Your WhatsApp is connected
### How It Works
When a user requests a magic link and has a phone number on file, you can send the magic link via WhatsApp instead of email:
```javascript
// Send magic link via WhatsApp
await fetch('/api/magic-link/send-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
channel: 'whatsapp', // Send via WhatsApp
phoneNumber: '+491234567890' // Required for WhatsApp
})
});
```
### Customizable Messages
Configure your WhatsApp message templates in **Settings -> Email & Messaging**:
**Available Variables:**
- `<%= URL %>` - Your frontend login URL
- `<%= CODE %>` - The magic link token
- `<%= APP_NAME %>` - Your app name
- `<%= EXPIRY_TEXT %>` - Token expiration text
**Example Template:**
```
*<%= APP_NAME %> Login*
Click the link below to log in:
<%= URL %>?loginToken=<%= CODE %>
This link expires in <%= EXPIRY_TEXT %>.
_If you didn't request this, please ignore this message._
```
### Frontend Login URL
Configure where users land when clicking the magic link:
1. Go to **Settings -> Email & Messaging**
2. Set **Frontend Login URL** (e.g., `https://yourapp.com/auth/login`)
3. Magic links will now redirect to your frontend
### Integration with Magic-Mail
If you have both **Magic-Link** and **Magic-Mail** plugins installed:
- Magic-Link delegates WhatsApp functionality to Magic-Mail
- Only ONE WhatsApp connection needed (managed by Magic-Mail)
- Navigate to Magic-Mail for WhatsApp setup
- Unified WhatsApp management across both plugins
---
## Prerequisites
This plugin requires a **configured email provider** to send magic link emails.
### Email Provider Setup
**Option 1: Nodemailer (Recommended)**
Install the Strapi email plugin:
```bash
npm install @strapi/provider-email-nodemailer
```
Configure in `config/plugins.js`:
```javascript
module.exports = ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: env('SMTP_HOST', 'smtp.gmail.com'),
port: env('SMTP_PORT', 587),
auth: {
user: env('SMTP_USERNAME'),
pass: env('SMTP_PASSWORD'),
},
},
settings: {
defaultFrom: env('SMTP_DEFAULT_FROM', 'noreply@example.com'),
defaultReplyTo: env('SMTP_DEFAULT_REPLY_TO', 'support@example.com'),
},
},
},
});
```
**Option 2: Other Email Providers**
You can use any Strapi-compatible email provider:
- SendGrid
- Mailgun
- Amazon SES
- Postmark
- Any SMTP service
See [Strapi Email Documentation](https://docs.strapi.io/dev-docs/plugins/email) for details.
### Email Designer 5 Integration (Optional)
This plugin is **fully compatible** with [Strapi Email Designer 5](https://www.npmjs.com/package/strapi-plugin-email-designer-5)!
```bash
# Install Email Designer 5
npm install strapi-plugin-email-designer-5
```
Once installed, you can:
- β
Create beautiful email templates in the visual designer
- β
Use template variables: `magicLink`, `token`, `user`, `expiresAt`
- β
Enable in Settings β Magic Link β Email Settings
---
## π Installation
```bash
# Using npm
npm install strapi-plugin-magic-link-v5
# Using yarn
yarn add strapi-plugin-magic-link-v5
# Using pnpm
pnpm add strapi-plugin-magic-link-v5
```
After installation, **restart your Strapi server**. The plugin will appear in your admin panel.
---
## π― Quick Start
### 1οΈβ£ First Time Setup - License Activation (Free)
**After installation, you'll see a license activation modal on first visit.**
Enter your details to activate the plugin (completely free):
```
Email Address: your-email@example.com
First Name: John
Last Name: Doe
```
Click **"Create License"** and you're done! The plugin will:
- β
Automatically register your installation
- β
Activate all features (no payment required)
- β
Connect to the license validation system
**Important:** This is a **free activation** - not a payment. It helps us track installations, provide support, and ensure security. You can also use an existing license key if you already have one.
### 2οΈβ£ Configure Settings
**Navigate to:** Settings β Magic Link β Settings
**Essential Configuration:**
```javascript
{
// Core Settings
"enabled": true,
"createUserIfNotExists": true, // Auto-create users on first login
"expire_period": 3600, // Token valid for 1 hour (3600s)
"token_length": 20, // Token security level (20-40 chars)
// Email Configuration
"from_email": "noreply@yourdomain.com",
"from_name": "Your App Name",
"object": "Your Magic Link Login",
"confirmationUrl": "https://yourdomain.com/auth/callback",
// Rate Limiting
"rate_limit_enabled": true,
"rate_limit_max_attempts": 5, // 5 requests per window
"rate_limit_window_minutes": 15 // 15 minute window
}
```
**Choose Authentication Mode:**
- Go to **Settings β MFA & Login Modes**
- Select your preferred mode (Magic Link Only is default and free)
- Configure OTP/TOTP settings if using Premium/Advanced features
### 3οΈβ£ Frontend Implementation
#### π Basic Magic Link Flow
**Step 1: Request a magic link**
```javascript
const response = await fetch('/api/magic-link/send-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
context: { redirectTo: '/dashboard' }
})
});
const data = await response.json();
console.log('Magic link sent!', data);
```
**Step 2: Verify token on callback page**
```javascript
const urlParams = new URLSearchParams(window.location.search);
const loginToken = urlParams.get('loginToken');
if (loginToken) {
try {
const response = await fetch(`/api/magic-link/login?loginToken=${loginToken}`);
const { jwt, user } = await response.json();
// Store JWT for authenticated requests
localStorage.setItem('token', jwt);
localStorage.setItem('user', JSON.stringify(user));
// Redirect to dashboard
window.location.href = '/dashboard';
} catch (error) {
console.error('Login failed:', error);
alert('Invalid or expired magic link');
}
}
```
#### π§ Email OTP Flow (Premium)
**Step 1: Request magic link (same as above)**
**Step 2: User clicks magic link β redirected to OTP page**
**Step 3: Verify OTP code**
```javascript
async function verifyOTP(email, otpCode) {
const response = await fetch('/api/magic-link/verify-otp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: email,
otp: otpCode
})
});
if (response.ok) {
const { jwt, user } = await response.json();
localStorage.setItem('token', jwt);
localStorage.setItem('user', JSON.stringify(user));
window.location.href = '/dashboard';
} else {
alert('Invalid OTP code. Please try again.');
}
}
// Usage
const otpInput = document.getElementById('otp-input');
verifyOTP('user@example.com', otpInput.value);
```
#### π TOTP Setup Flow (Advanced)
**Step 1: Setup TOTP for user (requires authentication)**
```javascript
async function setupTOTP() {
const token = localStorage.getItem('token');
const response = await fetch('/api/magic-link/totp/setup', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const { secret, qrCode, otpauthUrl } = await response.json();
// Display QR code to user
document.getElementById('qr-code').innerHTML = `
`;
// Show secret for manual entry
document.getElementById('secret').textContent = secret;
return secret;
}
```
**Step 2: Verify TOTP code**
```javascript
async function verifyTOTP(totpCode) {
const token = localStorage.getItem('token');
const response = await fetch('/api/magic-link/totp/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
token: totpCode
})
});
if (response.ok) {
alert('TOTP successfully enabled!');
return true;
} else {
alert('Invalid code. Please try again.');
return false;
}
}
```
**Step 3: Login with Email + TOTP (Alternative Login)**
```javascript
async function loginWithTOTP(email, totpCode) {
const response = await fetch('/api/magic-link/totp/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: email,
token: totpCode
})
});
if (response.ok) {
const { jwt, user } = await response.json();
localStorage.setItem('token', jwt);
localStorage.setItem('user', JSON.stringify(user));
window.location.href = '/dashboard';
} else {
alert('Invalid credentials');
}
}
```
#### π Check License Tier (Client-side)
```javascript
async function checkLicenseTier() {
const adminToken = localStorage.getItem('adminToken');
const response = await fetch('/magic-link/license/status', {
headers: {
'Authorization': `Bearer ${adminToken}`
}
});
const { data } = await response.json();
// data.features.premium - Email OTP available
// data.features.advanced - TOTP MFA available
// data.features.enterprise - All features
return {
isPremium: data.features?.premium || false,
isAdvanced: data.features?.advanced || false,
isEnterprise: data.features?.enterprise || false
};
}
// Usage
const license = await checkLicenseTier();
if (license.isPremium) {
console.log('Email OTP is available!');
}
```
---
## π‘ API Endpoints
### π Public Endpoints (No Auth Required)
#### Authentication
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/magic-link/send-link` | Generate and send magic link to email |
| `GET` | `/api/magic-link/login?loginToken=xxx` | Authenticate user with token |
| `POST` | `/api/magic-link/verify-otp` | Verify Email OTP code |
| `POST` | `/api/magic-link/totp/login` | Login with Email + TOTP code |
#### TOTP Management (User Auth Required)
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/magic-link/totp/setup` | Generate TOTP secret & QR code |
| `POST` | `/api/magic-link/totp/verify` | Verify and enable TOTP |
| `POST` | `/api/magic-link/totp/disable` | Disable TOTP for user |
| `GET` | `/api/magic-link/totp/status` | Check if TOTP is configured |
### π Admin Endpoints (Admin Auth Required)
#### Token Management
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/magic-link/tokens` | List all magic link tokens |
| `POST` | `/magic-link/tokens` | Create a new token |
| `DELETE` | `/magic-link/tokens/:id` | Permanently delete a token |
| `POST` | `/magic-link/tokens/:id/block` | Block a token |
| `POST` | `/magic-link/tokens/:id/activate` | Activate blocked token |
| `POST` | `/magic-link/tokens/:id/extend` | Extend token validity |
#### Security & Monitoring
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/magic-link/jwt-sessions` | List active JWT sessions |
| `POST` | `/magic-link/revoke-jwt` | Revoke a JWT session |
| `POST` | `/magic-link/ban-ip` | Ban an IP address |
| `DELETE` | `/magic-link/ban-ip/:ip` | Unban an IP address |
| `GET` | `/magic-link/banned-ips` | List all banned IPs |
| `GET` | `/magic-link/rate-limit/stats` | Get rate limit statistics |
| `POST` | `/magic-link/rate-limit/cleanup` | Cleanup expired entries |
#### OTP Management
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/magic-link/otp-codes` | List all OTP codes |
| `POST` | `/magic-link/otp-codes/cleanup` | Cleanup expired OTP codes |
#### License Management
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/magic-link/license/status` | Get current license status |
| `POST` | `/magic-link/license/auto-create` | Create license with admin data |
| `POST` | `/magic-link/license/activate` | Activate license with key |
---
## π Plugin Compatibility
Magic Link provides **compatibility modes** for seamless migration from other authentication plugins. No frontend code changes required!
### π strapi-plugin-passwordless Migration
If you're migrating from [strapi-plugin-passwordless](https://github.com/kucherenko/strapi-plugin-passwordless), enable the compatibility mode:
**Enable in Admin Panel:** Settings β Magic Link β Compatibility β "Passwordless Plugin Compatibility"
When enabled, these additional routes become available:
| Original Route | Magic Link Handler | Description |
|----------------|-------------------|-------------|
| `POST /api/passwordless/send-link` | `auth.sendLink` | Send magic link email |
| `GET /api/passwordless/login?loginToken=xxx` | `auth.login` | Authenticate with token |
**Request/Response Format:** Identical to the original plugin!
```javascript
// Your existing code works without changes:
const response = await fetch('/api/passwordless/send-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
username: 'John Doe', // Optional: username for new users
context: { redirectTo: '/dashboard' }
})
});
// Login response is identical:
// { jwt: "...", user: {...}, context: {...} }
```
**Migration Steps:**
1. Install `strapi-plugin-magic-link-v5`
2. Remove `strapi-plugin-passwordless` from `package.json`
3. Enable **Passwordless Compatibility** in Settings
4. Restart Strapi - done! β
**Gradual Migration:** You can later switch your frontend to use `/api/magic-link/*` endpoints and disable compatibility mode.
### π§ Email Designer 5 Integration
Magic Link is **fully compatible** with [strapi-plugin-email-designer-5](https://www.npmjs.com/package/strapi-plugin-email-designer-5)!
**Enable in Admin Panel:** Settings β Magic Link β Compatibility β "Email Designer 5 Integration"
```bash
# Install Email Designer 5
npm install strapi-plugin-email-designer-5
```
**Template Variables:**
| Variable | Description |
|----------|-------------|
| `{{magicLink}}` | Complete magic link URL with token |
| `{{token}}` | Raw token value |
| `{{user.email}}` | User's email address |
| `{{user.username}}` | User's username |
| `{{expiresAt}}` | Token expiration time |
**Configuration:**
1. Create a template in Email Designer
2. Go to Settings β Magic Link β Email Settings
3. Enable "Use Email Designer Template"
4. Select your template from the dropdown
### π‘οΈ Security Note
**Compatibility routes are disabled by default** for security. When disabled, accessing `/api/passwordless/*` returns a `404 Not Found` response - the routes appear to not exist.
---
## βοΈ Configuration
### π Authentication Mode Settings
**Configure in Admin Panel β Settings β MFA & Login Modes**
```javascript
{
// Authentication Modes (mutually exclusive)
"otp_enabled": false, // Enable Email OTP (Premium)
"mfa_require_totp": false, // Enable TOTP MFA (Advanced)
"totp_as_primary_auth": false, // Enable TOTP-only login (Advanced)
// Email OTP Configuration (Premium)
"otp_type": "email", // "email" | "sms" | "totp"
"otp_length": 6, // Code length (4-8 digits)
"otp_expiry": 300, // Validity in seconds (default: 5 min)
"otp_max_attempts": 3, // Max verification attempts
"otp_resend_cooldown": 60, // Cooldown before resend (seconds)
// TOTP Configuration (Advanced)
"totp_issuer": "Magic Link", // Displayed in authenticator app
"totp_digits": 6, // Code digits (6 or 8)
"totp_period": 30, // Time period (seconds)
"totp_algorithm": "sha1" // "sha1" | "sha256" | "sha512"
}
```
### βοΈ General Settings
```javascript
{
"enabled": true, // Enable/disable plugin
"createUserIfNotExists": true, // Auto-create users on first login
"expire_period": 3600, // Token expiration (seconds)
"token_length": 20, // Token security level (20-40)
"stays_valid": false, // Token reusable after first use
"max_login_attempts": 5, // Limit failed login attempts
"callback_url": "https://yourdomain.com/auth/callback"
}
```
### π§ Email Settings
```javascript
{
"from_name": "Your App",
"from_email": "noreply@yourdomain.com",
"response_email": "support@yourdomain.com",
"object": "Your Magic Link Login",
"message_html": "
Welcome!
Click to login: Login
",
"message_text": "Your login link: <%= URL %>?loginToken=<%= CODE %>"
}
```
**Template Variables:**
- `<%= URL %>` - Your confirmation URL
- `<%= CODE %>` - The generated token
- `<%= USER %>` - User email/username
- `<%= EXPIRES_AT %>` - Token expiration time
### π JWT Settings
```javascript
{
"use_jwt_token": true, // Use JWT for authentication
"jwt_token_expires_in": "30d", // JWT validity (e.g., '7d', '30d', '365d')
"store_login_info": true // Track IP and user agent
}
```
### π‘οΈ Security & Rate Limiting
```javascript
{
"rate_limit_enabled": true, // Enable rate limiting
"rate_limit_max_attempts": 5, // Max requests per window
"rate_limit_window_minutes": 15 // Time window in minutes
}
```
**How it works:**
- Limits token requests per IP address
- Limits token requests per email address
- Returns `429 Too Many Requests` when exceeded
- Automatic cleanup every 30 minutes
- Protects against brute-force and spam attacks
**Example:** With default settings (5 attempts per 15 minutes):
- User can request max 5 magic links in 15 minutes
- After 5 attempts, they must wait up to 15 minutes
- View statistics in Settings β Security & Rate Limiting
- Manually cleanup expired entries or reset all limits
---
## π Required Environment Variables
The plugin stores secrets that must be protected across restarts and should
not be rotated with the rest of your Strapi key material. Two env vars are
**mandatory in production** (the plugin refuses to boot without them):
```bash
# 32+ random bytes β used as AES-256 key for TOTP secrets.
# DO NOT ROTATE: existing TOTP secrets would become undecryptable.
MAGIC_LINK_ENCRYPTION_KEY=
# 16+ random bytes β pepper mixed into OTP code hashes to defeat
# rainbow-table attacks on stored 6-digit codes if the DB is ever leaked.
MAGIC_LINK_OTP_PEPPER=
```
In non-production environments (`NODE_ENV !== 'production'`) the plugin
falls back to derived values so local dev setups keep working β but a WARN
is logged once per process.
---
## π Reverse Proxy / Rate-Limit Trust
Rate limits and all audit logs identify the caller by `ctx.request.ip`,
which Koa derives from the TCP connection by default. If your Strapi is
behind a reverse proxy (nginx, Caddy, Cloudflare, ALB, β¦) you MUST:
1. Enable proxy trust in `config/server.{js,ts}`:
```js
module.exports = ({ env }) => ({
proxy: true, // Trust X-Forwarded-For from the proxy
// ... rest of config
});
```
2. Configure the proxy to **strip any incoming `X-Forwarded-For`** and
emit only its own value. Without this, an attacker can spoof the
header, defeating per-IP rate limits.
3. If you have multiple proxy hops, pin trust to the outermost ones via
Koa's `app.proxyIpHeader` / `app.maxIpsCount` (see Koa docs).
A misconfigured deployment will either:
- **Count all requests as the proxy's single IP** (everyone rate-limited together), or
- **Trust spoofed client IPs** (per-IP rate limit effectively disabled).
---
## π OTP Strict Binding
Starting with this release, `/otp/send`, `/otp/verify` and `/otp/resend`
require the client to present the `magicLinkToken` they received from
`/magic-link/login` when it returned `{ requiresOTP: true }`. This prevents
`/otp/send` from being abused as a standalone email-bombing vector against
arbitrary addresses and prevents `/otp/verify` from being a first-factor
login on its own.
If you have legacy integrations that must keep the old behaviour, disable
it via settings: `otp_strict_binding: false`. Doing so emits a WARN on
every request and is **not recommended**.
---
## π¨ Email Templates
Customize your magic link emails using template variables:
```html
Welcome!
Click to login:
Login to Your Account
```
**Available Variables:**
- `<%= URL %>` - Your confirmation URL
- `<%= CODE %>` - The generated token
---
## π Security Features
- **Token Expiration** - Configurable expiration periods
- **One-time Tokens** - Optional single-use tokens
- **IP Tracking** - Monitor login locations
- **IP Banning** - Block suspicious addresses
- **JWT Blacklist** - Revoke compromised sessions
- **Login Attempt Limiting** - Prevent brute force
- **User Agent Tracking** - Device fingerprinting
---
## π― Use Cases
- **SaaS Applications** - Simplify user onboarding
- **Customer Portals** - Secure, password-free access
- **Multi-tenant Systems** - Easy user management
- **Mobile Apps** - Seamless authentication flow
- **Content Platforms** - Reduce password fatigue
---
## π Troubleshooting
### Common Issues
| Issue | Solution |
|-------|----------|
| **Emails not sending** | Check Strapi email provider configuration in `config/plugins.js` |
| **Token invalid errors** | Verify token hasn't expired. Check `expire_period` setting |
| **User not found** | Enable `createUserIfNotExists` setting in admin panel |
| **License activation fails** | Check network connectivity. Try manual activation with license key |
| **npm install fails** | Use `npm install --legacy-peer-deps` or update to latest npm |
| **OTP settings not visible** | Check license tier. Email OTP requires Premium license |
| **TOTP not working** | Verify Advanced license is active. Check authenticator app time sync |
| **"Current License: Free" but Premium activated** | Refresh browser cache. License info loads from `/magic-link/license/status` |
| **Lock icons not centered** | Update to v4.16.1+. Mobile UI fixes are included |
| **Email OTP shown without license** | Update to v4.16.3+. License validation is fixed |
### Debug Mode
Enable debug logging in your Strapi instance:
```bash
# In development
NODE_ENV=development npm run develop
# Check logs in terminal for Magic Link debug output
```
### Reset Plugin
If you need to reset the plugin:
```bash
# 1. Stop Strapi
# 2. Clear plugin database entries
# 3. Restart Strapi
npm run develop
```
### Get Support
- π [GitHub Issues](https://github.com/Schero94/strapi-plugin-magic-link-v5/issues)
- π Check the README for updated examples
---
## π€ Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
**Commit Convention:** Follow [Conventional Commits](https://www.conventionalcommits.org/)
- `feat:` - New features
- `fix:` - Bug fixes
- `docs:` - Documentation changes
- `chore:` - Maintenance tasks
---
## Changelog
### v5.2.7 (2025-12-16) - Latest
**WhatsApp Integration**
- **WhatsApp Magic Links** - Send authentication links via WhatsApp messenger (FREE!)
- **Customizable Templates** - Configure WhatsApp message text with variables
- **Frontend URL Setting** - Set custom frontend login URLs for magic links
- **Magic-Mail Integration** - Automatic delegation when both plugins installed
- **QR Code Setup** - Easy WhatsApp connection via QR code scan
- **Debug Mode** - Optional verbose logging for WhatsApp connections
**New Settings:**
- `frontend_login_url` - Custom frontend URL for magic links
- `whatsapp_message_text` - WhatsApp message template
- `whatsapp_app_name` - App name displayed in WhatsApp messages
- `whatsapp_debug` - Enable/disable verbose WhatsApp logging
### v5.1.10 (2025-12-15)
**Plugin Compatibility Mode**
- **Passwordless Plugin Compatibility** - Migration support for `strapi-plugin-passwordless`
- Added `/api/passwordless/send-link` and `/api/passwordless/login` routes
- Identical request/response format - no frontend changes needed!
- Disabled by default for security (returns 404 when off)
- **Email Designer 5 Integration** - Enhanced template support
- New "Compatibility" settings section in admin UI
- Full i18n support for compatibility settings (DE, EN, ES, FR, PT)
- `compatibility-check` policy for secure route access
### v4.16.3 (2025-11-22)
**License Validation Fix**
- β
Fixed license tier detection from backend features
- β
Correctly extracts `premium`, `advanced`, `enterprise` from license response
- β
Enriched license info with `tier` field for UI compatibility
- π Fixed "Current License: Free" display bug when Premium is active
### v4.16.2 (2025-11-22)
**Active State & Auto-Disable Fix**
- β
"Magic Link Only" now always shows ACTIVE with FREE license
- β
Email OTP settings only visible with Premium+ license
- β
Auto-disable premium features when license doesn't support them
- β
Added useEffect hook for automatic license enforcement
### v4.16.1 (2025-11-22)
**Lock Icons & License Checks**
- β
Perfectly centered lock/lightning icons in locked mode cards
- β
Fixed license validation in active state detection
- β
Locked features now show proper disabled state
- π¨ Improved locked overlay with flexbox centering
### v4.16.0 (2025-11-22)
**Major UX Improvements**
- π¨ Complete MFA settings redesign with modern UI
- π License tier status display with visual indicators
- π Locked feature cards with clear premium/advanced badges
- π©πͺ Full German translations for all MFA settings
- β
Improved explanations and help texts
- π§ Better Email OTP configuration section
- π Added step-by-step "How it works" guides
### v4.15.6 (2025-11-22)
**Email OTP Configuration**
- β
Added Email OTP configuration section
- βοΈ Code length, expiry time, max attempts settings
- π Resend cooldown configuration
- π§ OTP delivery method selection
- βΉοΈ Improved help text and info boxes
### v4.15.5 (2025-11-22)
**Mobile & Settings UI Fixes**
- π± Fixed mobile button icon centering issues
- ποΈ Removed duplicate OTP accordion
- π¨ Improved badge colors for better readability
- β¨ Better contrast for Premium/Advanced badges
For full version history, see [GitHub Releases](https://github.com/Schero94/strapi-plugin-magic-link-v5/releases).
---
## π License
This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.
**Important:** While the code is open source, the license validation system must remain intact. This ensures quality, security, and continued development of the plugin.
---
## π¬ Support & Community
### Need Help?
- π **Bug Reports**: [GitHub Issues](https://github.com/Schero94/strapi-plugin-magic-link-v5/issues)
- π‘ **Feature Requests**: [GitHub Discussions](https://github.com/Schero94/strapi-plugin-magic-link-v5/discussions)
- π¦ **npm Package**: [strapi-plugin-magic-link-v5](https://www.npmjs.com/package/strapi-plugin-magic-link-v5)
### Resources
- π **Documentation**: This README + [Strapi Docs](https://docs.strapi.io)
- π» **Source Code**: [GitHub Repository](https://github.com/Schero94/strapi-plugin-magic-link-v5)
- π― **Live Example**: Coming soon
- πΉ **Video Tutorial**: Coming soon
### Sponsoring
If this plugin saves you time and you'd like to support development:
- β Star the repository on GitHub
- π Report bugs and suggest features
- π‘ Contribute code improvements
- π’ Share with the Strapi community
---
## Security Advisories & Dependency Overrides
This plugin ships with a first-party dependency tree audited on every
release, but two transitive paths are governed by upstream packages we
do not control. If your own `npm audit` flags any of the following,
add the matching entry to your **Strapi project's** `package.json`
(not this plugin's β npm `overrides` only take effect on the
root project):
```jsonc
{
"overrides": {
// GHSA-xq3m-2v4x-88gg β protobufjs <7.5.5 arbitrary code execution.
// Reaches us via @whiskeysockets/baileys β libsignal β protobufjs.
// We already ship this override for local development; apply it in
// your Strapi project so the fix propagates to the installed tree.
"protobufjs": "^7.5.5",
// Optional: if you do not use the WhatsApp delivery feature, you
// can also skip installing @whiskeysockets/baileys altogether:
// npm install --omit=optional
// (The Magic-Link code path detects the missing module and
// disables the WhatsApp controller at runtime β the rest of the
// plugin keeps working.)
}
}
```
After editing `package.json`, run:
```bash
rm -rf node_modules package-lock.json
npm install
npm audit
```
The critical `protobufjs` advisory disappears once the override is in
place. The remaining `@strapi/design-system β lodash` advisory is a
Strapi-core issue and will clear up automatically when you upgrade
`@strapi/strapi` to the latest patch release.
---
**Made with β€οΈ by [Schero94](https://github.com/Schero94)**
*Passwordless authentication, simplified.*
[](https://github.com/Schero94/strapi-plugin-magic-link-v5)
[](https://www.npmjs.com/package/strapi-plugin-magic-link-v5)