Higher Quality, Stronger Performance, Increased Stability, Better Developer Experience, discover everything we've shipped recently!

Strapi plugin logo for Magic Link - Passwordless Authentication for Strapi v5

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.

thumbnail for Magic Link - Passwordless Authentication for Strapi v5

🔐 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: MIT npm version Strapi v5

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

🌍 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.

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:

ModeDescription
Magic Link OnlyOne-click email login — fast & user-friendly
Magic Link via WhatsAppSend magic links via the WhatsApp messenger
Magic Link + Email OTP6-digit code sent after the magic link click
Magic Link + TOTP (MFA)Authenticator app (Google Authenticator, Authy, …)
TOTP-Only LoginDirect 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.

Token Dashboard

Create New Token

Simple modal to create tokens with custom TTL and context data.

Create Token

JWT Session Management

Monitor and manage all active JWT sessions across your application.

JWT Sessions

IP Ban Management

Security feature to block suspicious IP addresses.

IP Bans

Settings Interface

Comprehensive settings panel with modern UI.

Settings Overview

General Settings

Configure core functionality and authentication options.

General Settings


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:

// 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:

npm install @strapi/provider-email-nodemailer

Configure in config/plugins.js:

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 for details.

Email Designer 5 Integration (Optional)

This plugin is fully compatible with Strapi Email Designer 5!

# 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

# 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:

{
  // 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

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

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

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)

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 = `<img src="${qrCode}" alt="Scan with authenticator app" />`;
  
  // Show secret for manual entry
  document.getElementById('secret').textContent = secret;
  
  return secret;
}

Step 2: Verify TOTP code

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)

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)

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

MethodEndpointDescription
POST/api/magic-link/send-linkGenerate and send magic link to email
GET/api/magic-link/login?loginToken=xxxAuthenticate user with token
POST/api/magic-link/verify-otpVerify Email OTP code
POST/api/magic-link/totp/loginLogin with Email + TOTP code

TOTP Management (User Auth Required)

MethodEndpointDescription
POST/api/magic-link/totp/setupGenerate TOTP secret & QR code
POST/api/magic-link/totp/verifyVerify and enable TOTP
POST/api/magic-link/totp/disableDisable TOTP for user
GET/api/magic-link/totp/statusCheck if TOTP is configured

🔐 Admin Endpoints (Admin Auth Required)

Token Management

MethodEndpointDescription
GET/magic-link/tokensList all magic link tokens
POST/magic-link/tokensCreate a new token
DELETE/magic-link/tokens/:idPermanently delete a token
POST/magic-link/tokens/:id/blockBlock a token
POST/magic-link/tokens/:id/activateActivate blocked token
POST/magic-link/tokens/:id/extendExtend token validity

Security & Monitoring

MethodEndpointDescription
GET/magic-link/jwt-sessionsList active JWT sessions
POST/magic-link/revoke-jwtRevoke a JWT session
POST/magic-link/ban-ipBan an IP address
DELETE/magic-link/ban-ip/:ipUnban an IP address
GET/magic-link/banned-ipsList all banned IPs
GET/magic-link/rate-limit/statsGet rate limit statistics
POST/magic-link/rate-limit/cleanupCleanup expired entries

OTP Management

MethodEndpointDescription
GET/magic-link/otp-codesList all OTP codes
POST/magic-link/otp-codes/cleanupCleanup expired OTP codes

License Management

MethodEndpointDescription
GET/magic-link/license/statusGet current license status
POST/magic-link/license/auto-createCreate license with admin data
POST/magic-link/license/activateActivate 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, enable the compatibility mode:

Enable in Admin Panel: Settings → Magic Link → Compatibility → "Passwordless Plugin Compatibility"

When enabled, these additional routes become available:

Original RouteMagic Link HandlerDescription
POST /api/passwordless/send-linkauth.sendLinkSend magic link email
GET /api/passwordless/login?loginToken=xxxauth.loginAuthenticate with token

Request/Response Format: Identical to the original plugin!

// 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!

Enable in Admin Panel: Settings → Magic Link → Compatibility → "Email Designer 5 Integration"

# 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

{
  // 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

{
  "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

{
  "from_name": "Your App",
  "from_email": "noreply@yourdomain.com",
  "response_email": "support@yourdomain.com",
  "object": "Your Magic Link Login",
  "message_html": "<h1>Welcome!</h1><p>Click to login: <a href='<%= URL %>?loginToken=<%= CODE %>'>Login</a></p>",
  "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

{
  "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

{
  "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):

# 32+ random bytes — used as AES-256 key for TOTP secrets.
# DO NOT ROTATE: existing TOTP secrets would become undecryptable.
MAGIC_LINK_ENCRYPTION_KEY=<hex from: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))">

# 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=<hex from: node -e "console.log(require('crypto').randomBytes(24).toString('hex'))">

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}:

    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 Template -->
<h1>Welcome!</h1>
<p>Click to login:</p>
<a href="<%= URL %>?loginToken=<%= CODE %>">
  Login to Your Account
</a>

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

IssueSolution
Emails not sendingCheck Strapi email provider configuration in config/plugins.js
Token invalid errorsVerify token hasn't expired. Check expire_period setting
User not foundEnable createUserIfNotExists setting in admin panel
License activation failsCheck network connectivity. Try manual activation with license key
npm install failsUse npm install --legacy-peer-deps or update to latest npm
OTP settings not visibleCheck license tier. Email OTP requires Premium license
TOTP not workingVerify Advanced license is active. Check authenticator app time sync
"Current License: Free" but Premium activatedRefresh browser cache. License info loads from /magic-link/license/status
Lock icons not centeredUpdate to v4.16.1+. Mobile UI fixes are included
Email OTP shown without licenseUpdate to v4.16.3+. License validation is fixed

Debug Mode

Enable debug logging in your Strapi instance:

# 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:

# 1. Stop Strapi
# 2. Clear plugin database entries
# 3. Restart Strapi
npm run develop

Get Support


🤝 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

  • 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.


📄 License

This project is licensed under the MIT License - see the 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?

Resources

  • 📖 Documentation: This README + Strapi Docs
  • 💻 Source Code: GitHub Repository
  • 🎯 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):

{
  "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:

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

Passwordless authentication, simplified.

GitHub npm

Install now

npm install strapi-plugin-magic-link-v5

STATS

12 GitHub stars205 weekly downloads

Last updated

10 days ago

Strapi Version

5.0.0 and above

Author

github profile image for Schero A.
Schero A.

Useful links

Create your own plugin

Check out the available plugin resources that will help you to develop your plugin or provider and get it listed on the marketplace.