{"id":42876812,"url":"https://github.com/schero94/strapi-plugin-magic-link-v5","last_synced_at":"2026-05-03T12:03:16.613Z","repository":{"id":284000519,"uuid":"953515112","full_name":"Schero94/strapi-plugin-magic-link-v5","owner":"Schero94","description":"Secure passwordless authentication for Strapi using email-based magic links. No passwords required.","archived":false,"fork":false,"pushed_at":"2026-04-26T19:44:29.000Z","size":6080,"stargazers_count":12,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-26T21:28:52.176Z","etag":null,"topics":["authentication","magiclink","passwordless","passwordless-auth","passwordless-authentication","passwordlessauthentication","plugin","plugin-strapi","strapi","strapi-plugin"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Schero94.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-23T14:49:01.000Z","updated_at":"2026-04-26T19:44:31.000Z","dependencies_parsed_at":"2025-12-05T10:07:46.122Z","dependency_job_id":null,"html_url":"https://github.com/Schero94/strapi-plugin-magic-link-v5","commit_stats":null,"previous_names":["begservice/magic-link","begservice/strapi-plugin-magic-link-v5"],"tags_count":93,"template":false,"template_full_name":null,"purl":"pkg:github/Schero94/strapi-plugin-magic-link-v5","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Schero94%2Fstrapi-plugin-magic-link-v5","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Schero94%2Fstrapi-plugin-magic-link-v5/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Schero94%2Fstrapi-plugin-magic-link-v5/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Schero94%2Fstrapi-plugin-magic-link-v5/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Schero94","download_url":"https://codeload.github.com/Schero94/strapi-plugin-magic-link-v5/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Schero94%2Fstrapi-plugin-magic-link-v5/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32568036,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T06:36:36.687Z","status":"ssl_error","status_checked_at":"2026-05-03T06:36:09.306Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","magiclink","passwordless","passwordless-auth","passwordless-authentication","passwordlessauthentication","plugin","plugin-strapi","strapi","strapi-plugin"],"created_at":"2026-01-30T14:10:43.636Z","updated_at":"2026-05-03T12:03:16.595Z","avatar_url":"https://github.com/Schero94.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🔐 Magic Link - Passwordless Authentication for Strapi v5\n\n**The most advanced passwordless authentication plugin for Strapi v5**\n\nSecure, modern, and user-friendly authentication using email-based magic links, OTP codes, and TOTP authenticators. Built for production with enterprise-grade security features.\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![npm version](https://badge.fury.io/js/strapi-plugin-magic-link-v5.svg)](https://www.npmjs.com/package/strapi-plugin-magic-link-v5)\n[![Strapi v5](https://img.shields.io/badge/Strapi-v5-7C3AED.svg)](https://strapi.io)\n\n## Why Magic Link?\n\n- **Zero Setup Time** - Install, configure SMTP, and start using.\n- **Production Ready** - Battle-tested with rate limiting, IP bans, and session management.\n- **Multi-Factor Authentication** - Email OTP + TOTP Authenticator support, included.\n- **WhatsApp Integration** - Send magic links via WhatsApp messenger.\n- **Beautiful UI** - Modern, responsive admin interface localized in 5 languages.\n- **Free \u0026 Open** - MIT license, no paywall, every feature available out of the box.\n\n## 📚 Quick Links\n\n- [✨ Features](#-features) - Authentication modes, security, license tiers\n- [🚀 Installation](#-installation) - Get started in minutes\n- [🎯 Quick Start](#-quick-start) - License activation \u0026 configuration\n- [💻 Frontend Examples](#-frontend-implementation) - Magic Link, Email OTP, TOTP flows\n- [📡 API Endpoints](#-api-endpoints) - Complete API reference\n- [🔄 Plugin Compatibility](#-plugin-compatibility) - Migration from other plugins\n- [⚙️ Configuration](#-configuration) - All settings explained\n- [🐛 Troubleshooting](#-troubleshooting) - Common issues \u0026 solutions\n- [📝 Changelog](#-changelog) - Version history \u0026 updates\n\n## 🌍 Supported Languages\n\nThe admin interface is available in **5 languages** for international accessibility:\n\n- 🇬🇧 **English** - Global standard\n- 🇩🇪 **Deutsch** - German (DACH region)\n- 🇫🇷 **Français** - French (Strapi's home \u0026 community)\n- 🇪🇸 **Español** - Spanish (Spain \u0026 Latin America)\n- 🇵🇹 **Português** - Portuguese (Brazil \u0026 Portugal)\n\nUsers can switch languages in **Settings → Magic Link → Interface Language**.\n\n---\n\n## 📜 License\n\nMIT-licensed. Free for personal and commercial use. See [LICENSE](./LICENSE).\n\n### Optional license-key activation\n\nThe admin panel offers a \"License\" tab where you can register an optional\nlicense key. This is purely cosmetic — it does **not** unlock any\nfeature, and the plugin is fully usable without it. Registering a key\njust lets the project track installs and helps us prioritise issues.\n\nYou can ignore the License tab entirely; nothing will be gated.\n\n---\n\n## ✨ Features\n\n### Authentication Modes\n\nAll modes are available in every install — pick whichever fits your security profile:\n\n| Mode | Description |\n|------|-------------|\n| **Magic Link Only** | One-click email login — fast \u0026 user-friendly |\n| **Magic Link via WhatsApp** | Send magic links via the WhatsApp messenger |\n| **Magic Link + Email OTP** | 6-digit code sent after the magic link click |\n| **Magic Link + TOTP (MFA)** | Authenticator app (Google Authenticator, Authy, …) |\n| **TOTP-Only Login** | Direct login with email + TOTP code |\n\n### 🛡️ Security Features\n\n- **Rate Limiting** — 5 requests per 15 minutes (configurable)\n- **IP Banning** — Block suspicious addresses with one click\n- **Session Management** — Monitor and revoke active JWT sessions\n- **Login Tracking** — Store IP addresses and user agents\n- **Token Expiration** — Configurable validity periods\n- **Brute Force Protection** — Login attempt limiting\n- **Security Score** — Real-time configuration assessment\n\n### 🎨 Admin Interface\n\n- **Modern Dashboard** - Beautiful token statistics and charts\n- **Mobile Optimized** - Perfect icon centering on all screen sizes\n- **German Translations** - Fully localized UI (5 languages total)\n- **License Management** - Visual tier display and feature unlocking\n- **Professional Token Management** - Create, extend, block, delete\n- **Search \u0026 Filter** - Find anything in seconds\n- **Bulk Operations** - Manage multiple items at once\n\n### Customization\n\n- **Email Templates** - HTML \u0026 plain text with variables\n- **WhatsApp Templates** - Customizable WhatsApp message content\n- **Email Designer 5** - Visual email builder integration\n- **Flexible Configuration** - All settings in admin panel\n- **Frontend URL** - Configure custom frontend login URLs\n- **Custom Callbacks** - Post-login redirect URLs\n- **Auto User Creation** - Optional on first login\n- **Token Reusability** - One-time or multi-use tokens\n\n---\n\n## 📸 Screenshots\n\n### Token Management Dashboard\nProfessional interface for managing magic link tokens with real-time statistics.\n\n![Token Dashboard](pics/token-dashboard.png)\n\n### Create New Token\nSimple modal to create tokens with custom TTL and context data.\n\n![Create Token](pics/createToken.png)\n\n### JWT Session Management\nMonitor and manage all active JWT sessions across your application.\n\n![JWT Sessions](pics/jwt-dashboard.png)\n\n### IP Ban Management\nSecurity feature to block suspicious IP addresses.\n\n![IP Bans](pics/ipban-dashboard.png)\n\n### Settings Interface\nComprehensive settings panel with modern UI.\n\n![Settings Overview](pics/settings.png)\n\n### General Settings\nConfigure core functionality and authentication options.\n\n![General Settings](pics/settings-general.png)\n\n---\n\n## WhatsApp Integration (NEW!)\n\nMagic Link now supports sending authentication links via **WhatsApp** - completely FREE!\n\n### Why WhatsApp?\n\n- **Higher Open Rates** - 98% open rate vs 20% for email\n- **Instant Delivery** - No spam folders, no delays\n- **User Preference** - Many users prefer messaging over email\n- **FREE** - No additional costs, uses your own WhatsApp number\n\n### Quick Setup\n\n1. Navigate to **Admin Panel -\u003e Magic Link -\u003e WhatsApp**\n2. Scan the QR code with your WhatsApp mobile app\n3. Done! Your WhatsApp is connected\n\n### How It Works\n\nWhen a user requests a magic link and has a phone number on file, you can send the magic link via WhatsApp instead of email:\n\n```javascript\n// Send magic link via WhatsApp\nawait fetch('/api/magic-link/send-link', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    email: 'user@example.com',\n    channel: 'whatsapp',  // Send via WhatsApp\n    phoneNumber: '+491234567890'  // Required for WhatsApp\n  })\n});\n```\n\n### Customizable Messages\n\nConfigure your WhatsApp message templates in **Settings -\u003e Email \u0026 Messaging**:\n\n**Available Variables:**\n- `\u003c%= URL %\u003e` - Your frontend login URL\n- `\u003c%= CODE %\u003e` - The magic link token\n- `\u003c%= APP_NAME %\u003e` - Your app name\n- `\u003c%= EXPIRY_TEXT %\u003e` - Token expiration text\n\n**Example Template:**\n```\n*\u003c%= APP_NAME %\u003e Login*\n\nClick the link below to log in:\n\n\u003c%= URL %\u003e?loginToken=\u003c%= CODE %\u003e\n\nThis link expires in \u003c%= EXPIRY_TEXT %\u003e.\n\n_If you didn't request this, please ignore this message._\n```\n\n### Frontend Login URL\n\nConfigure where users land when clicking the magic link:\n\n1. Go to **Settings -\u003e Email \u0026 Messaging**\n2. Set **Frontend Login URL** (e.g., `https://yourapp.com/auth/login`)\n3. Magic links will now redirect to your frontend\n\n### Integration with Magic-Mail\n\nIf you have both **Magic-Link** and **Magic-Mail** plugins installed:\n\n- Magic-Link delegates WhatsApp functionality to Magic-Mail\n- Only ONE WhatsApp connection needed (managed by Magic-Mail)\n- Navigate to Magic-Mail for WhatsApp setup\n- Unified WhatsApp management across both plugins\n\n---\n\n## Prerequisites\n\nThis plugin requires a **configured email provider** to send magic link emails.\n\n### Email Provider Setup\n\n**Option 1: Nodemailer (Recommended)**\n\nInstall the Strapi email plugin:\n\n```bash\nnpm install @strapi/provider-email-nodemailer\n```\n\nConfigure in `config/plugins.js`:\n\n```javascript\nmodule.exports = ({ env }) =\u003e ({\n  email: {\n    config: {\n      provider: 'nodemailer',\n      providerOptions: {\n        host: env('SMTP_HOST', 'smtp.gmail.com'),\n        port: env('SMTP_PORT', 587),\n        auth: {\n          user: env('SMTP_USERNAME'),\n          pass: env('SMTP_PASSWORD'),\n        },\n      },\n      settings: {\n        defaultFrom: env('SMTP_DEFAULT_FROM', 'noreply@example.com'),\n        defaultReplyTo: env('SMTP_DEFAULT_REPLY_TO', 'support@example.com'),\n      },\n    },\n  },\n});\n```\n\n**Option 2: Other Email Providers**\n\nYou can use any Strapi-compatible email provider:\n- SendGrid\n- Mailgun  \n- Amazon SES\n- Postmark\n- Any SMTP service\n\nSee [Strapi Email Documentation](https://docs.strapi.io/dev-docs/plugins/email) for details.\n\n### Email Designer 5 Integration (Optional)\n\nThis plugin is **fully compatible** with [Strapi Email Designer 5](https://www.npmjs.com/package/strapi-plugin-email-designer-5)!\n\n```bash\n# Install Email Designer 5\nnpm install strapi-plugin-email-designer-5\n```\n\nOnce installed, you can:\n- ✅ Create beautiful email templates in the visual designer\n- ✅ Use template variables: `magicLink`, `token`, `user`, `expiresAt`\n- ✅ Enable in Settings → Magic Link → Email Settings\n\n---\n\n## 🚀 Installation\n\n```bash\n# Using npm\nnpm install strapi-plugin-magic-link-v5\n\n# Using yarn\nyarn add strapi-plugin-magic-link-v5\n\n# Using pnpm\npnpm add strapi-plugin-magic-link-v5\n```\n\nAfter installation, **restart your Strapi server**. The plugin will appear in your admin panel.\n\n---\n\n## 🎯 Quick Start\n\n### 1️⃣ First Time Setup - License Activation (Free)\n\n**After installation, you'll see a license activation modal on first visit.**\n\nEnter your details to activate the plugin (completely free):\n\n```\nEmail Address: your-email@example.com\nFirst Name: John\nLast Name: Doe\n```\n\nClick **\"Create License\"** and you're done! The plugin will:\n- ✅ Automatically register your installation\n- ✅ Activate all features (no payment required)\n- ✅ Connect to the license validation system\n\n**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.\n\n### 2️⃣ Configure Settings\n\n**Navigate to:** Settings → Magic Link → Settings\n\n**Essential Configuration:**\n\n```javascript\n{\n  // Core Settings\n  \"enabled\": true,\n  \"createUserIfNotExists\": true,    // Auto-create users on first login\n  \"expire_period\": 3600,             // Token valid for 1 hour (3600s)\n  \"token_length\": 20,                // Token security level (20-40 chars)\n  \n  // Email Configuration\n  \"from_email\": \"noreply@yourdomain.com\",\n  \"from_name\": \"Your App Name\",\n  \"object\": \"Your Magic Link Login\",\n  \"confirmationUrl\": \"https://yourdomain.com/auth/callback\",\n  \n  // Rate Limiting\n  \"rate_limit_enabled\": true,\n  \"rate_limit_max_attempts\": 5,      // 5 requests per window\n  \"rate_limit_window_minutes\": 15    // 15 minute window\n}\n```\n\n**Choose Authentication Mode:**\n- Go to **Settings → MFA \u0026 Login Modes**\n- Select your preferred mode (Magic Link Only is default and free)\n- Configure OTP/TOTP settings if using Premium/Advanced features\n\n### 3️⃣ Frontend Implementation\n\n#### 🔗 Basic Magic Link Flow\n\n**Step 1: Request a magic link**\n```javascript\nconst response = await fetch('/api/magic-link/send-link', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    email: 'user@example.com',\n    context: { redirectTo: '/dashboard' }\n  })\n});\n\nconst data = await response.json();\nconsole.log('Magic link sent!', data);\n```\n\n**Step 2: Verify token on callback page**\n```javascript\nconst urlParams = new URLSearchParams(window.location.search);\nconst loginToken = urlParams.get('loginToken');\n\nif (loginToken) {\n  try {\n    const response = await fetch(`/api/magic-link/login?loginToken=${loginToken}`);\n    const { jwt, user } = await response.json();\n    \n    // Store JWT for authenticated requests\n    localStorage.setItem('token', jwt);\n    localStorage.setItem('user', JSON.stringify(user));\n    \n    // Redirect to dashboard\n    window.location.href = '/dashboard';\n  } catch (error) {\n    console.error('Login failed:', error);\n    alert('Invalid or expired magic link');\n  }\n}\n```\n\n#### 📧 Email OTP Flow (Premium)\n\n**Step 1: Request magic link (same as above)**\n\n**Step 2: User clicks magic link → redirected to OTP page**\n\n**Step 3: Verify OTP code**\n```javascript\nasync function verifyOTP(email, otpCode) {\n  const response = await fetch('/api/magic-link/verify-otp', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      email: email,\n      otp: otpCode\n    })\n  });\n\n  if (response.ok) {\n    const { jwt, user } = await response.json();\n    localStorage.setItem('token', jwt);\n    localStorage.setItem('user', JSON.stringify(user));\n    window.location.href = '/dashboard';\n  } else {\n    alert('Invalid OTP code. Please try again.');\n  }\n}\n\n// Usage\nconst otpInput = document.getElementById('otp-input');\nverifyOTP('user@example.com', otpInput.value);\n```\n\n#### 🔐 TOTP Setup Flow (Advanced)\n\n**Step 1: Setup TOTP for user (requires authentication)**\n```javascript\nasync function setupTOTP() {\n  const token = localStorage.getItem('token');\n  \n  const response = await fetch('/api/magic-link/totp/setup', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${token}`,\n      'Content-Type': 'application/json'\n    }\n  });\n\n  const { secret, qrCode, otpauthUrl } = await response.json();\n  \n  // Display QR code to user\n  document.getElementById('qr-code').innerHTML = `\u003cimg src=\"${qrCode}\" alt=\"Scan with authenticator app\" /\u003e`;\n  \n  // Show secret for manual entry\n  document.getElementById('secret').textContent = secret;\n  \n  return secret;\n}\n```\n\n**Step 2: Verify TOTP code**\n```javascript\nasync function verifyTOTP(totpCode) {\n  const token = localStorage.getItem('token');\n  \n  const response = await fetch('/api/magic-link/totp/verify', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${token}`,\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({\n      token: totpCode\n    })\n  });\n\n  if (response.ok) {\n    alert('TOTP successfully enabled!');\n    return true;\n  } else {\n    alert('Invalid code. Please try again.');\n    return false;\n  }\n}\n```\n\n**Step 3: Login with Email + TOTP (Alternative Login)**\n```javascript\nasync function loginWithTOTP(email, totpCode) {\n  const response = await fetch('/api/magic-link/totp/login', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      email: email,\n      token: totpCode\n    })\n  });\n\n  if (response.ok) {\n    const { jwt, user } = await response.json();\n    localStorage.setItem('token', jwt);\n    localStorage.setItem('user', JSON.stringify(user));\n    window.location.href = '/dashboard';\n  } else {\n    alert('Invalid credentials');\n  }\n}\n```\n\n#### 🔍 Check License Tier (Client-side)\n\n```javascript\nasync function checkLicenseTier() {\n  const adminToken = localStorage.getItem('adminToken');\n  \n  const response = await fetch('/magic-link/license/status', {\n    headers: {\n      'Authorization': `Bearer ${adminToken}`\n    }\n  });\n\n  const { data } = await response.json();\n  \n  // data.features.premium - Email OTP available\n  // data.features.advanced - TOTP MFA available\n  // data.features.enterprise - All features\n  \n  return {\n    isPremium: data.features?.premium || false,\n    isAdvanced: data.features?.advanced || false,\n    isEnterprise: data.features?.enterprise || false\n  };\n}\n\n// Usage\nconst license = await checkLicenseTier();\nif (license.isPremium) {\n  console.log('Email OTP is available!');\n}\n```\n\n---\n\n## 📡 API Endpoints\n\n### 🌐 Public Endpoints (No Auth Required)\n\n#### Authentication\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/api/magic-link/send-link` | Generate and send magic link to email |\n| `GET` | `/api/magic-link/login?loginToken=xxx` | Authenticate user with token |\n| `POST` | `/api/magic-link/verify-otp` | Verify Email OTP code |\n| `POST` | `/api/magic-link/totp/login` | Login with Email + TOTP code |\n\n#### TOTP Management (User Auth Required)\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/api/magic-link/totp/setup` | Generate TOTP secret \u0026 QR code |\n| `POST` | `/api/magic-link/totp/verify` | Verify and enable TOTP |\n| `POST` | `/api/magic-link/totp/disable` | Disable TOTP for user |\n| `GET` | `/api/magic-link/totp/status` | Check if TOTP is configured |\n\n### 🔐 Admin Endpoints (Admin Auth Required)\n\n#### Token Management\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/magic-link/tokens` | List all magic link tokens |\n| `POST` | `/magic-link/tokens` | Create a new token |\n| `DELETE` | `/magic-link/tokens/:id` | Permanently delete a token |\n| `POST` | `/magic-link/tokens/:id/block` | Block a token |\n| `POST` | `/magic-link/tokens/:id/activate` | Activate blocked token |\n| `POST` | `/magic-link/tokens/:id/extend` | Extend token validity |\n\n#### Security \u0026 Monitoring\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/magic-link/jwt-sessions` | List active JWT sessions |\n| `POST` | `/magic-link/revoke-jwt` | Revoke a JWT session |\n| `POST` | `/magic-link/ban-ip` | Ban an IP address |\n| `DELETE` | `/magic-link/ban-ip/:ip` | Unban an IP address |\n| `GET` | `/magic-link/banned-ips` | List all banned IPs |\n| `GET` | `/magic-link/rate-limit/stats` | Get rate limit statistics |\n| `POST` | `/magic-link/rate-limit/cleanup` | Cleanup expired entries |\n\n#### OTP Management\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/magic-link/otp-codes` | List all OTP codes |\n| `POST` | `/magic-link/otp-codes/cleanup` | Cleanup expired OTP codes |\n\n#### License Management\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/magic-link/license/status` | Get current license status |\n| `POST` | `/magic-link/license/auto-create` | Create license with admin data |\n| `POST` | `/magic-link/license/activate` | Activate license with key |\n\n---\n\n## 🔄 Plugin Compatibility\n\nMagic Link provides **compatibility modes** for seamless migration from other authentication plugins. No frontend code changes required!\n\n### 🔗 strapi-plugin-passwordless Migration\n\nIf you're migrating from [strapi-plugin-passwordless](https://github.com/kucherenko/strapi-plugin-passwordless), enable the compatibility mode:\n\n**Enable in Admin Panel:** Settings → Magic Link → Compatibility → \"Passwordless Plugin Compatibility\"\n\nWhen enabled, these additional routes become available:\n\n| Original Route | Magic Link Handler | Description |\n|----------------|-------------------|-------------|\n| `POST /api/passwordless/send-link` | `auth.sendLink` | Send magic link email |\n| `GET /api/passwordless/login?loginToken=xxx` | `auth.login` | Authenticate with token |\n\n**Request/Response Format:** Identical to the original plugin!\n\n```javascript\n// Your existing code works without changes:\nconst response = await fetch('/api/passwordless/send-link', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    email: 'user@example.com',\n    username: 'John Doe',      // Optional: username for new users\n    context: { redirectTo: '/dashboard' }\n  })\n});\n\n// Login response is identical:\n// { jwt: \"...\", user: {...}, context: {...} }\n```\n\n**Migration Steps:**\n1. Install `strapi-plugin-magic-link-v5`\n2. Remove `strapi-plugin-passwordless` from `package.json`\n3. Enable **Passwordless Compatibility** in Settings\n4. Restart Strapi - done! ✅\n\n**Gradual Migration:** You can later switch your frontend to use `/api/magic-link/*` endpoints and disable compatibility mode.\n\n### 📧 Email Designer 5 Integration\n\nMagic Link is **fully compatible** with [strapi-plugin-email-designer-5](https://www.npmjs.com/package/strapi-plugin-email-designer-5)!\n\n**Enable in Admin Panel:** Settings → Magic Link → Compatibility → \"Email Designer 5 Integration\"\n\n```bash\n# Install Email Designer 5\nnpm install strapi-plugin-email-designer-5\n```\n\n**Template Variables:**\n| Variable | Description |\n|----------|-------------|\n| `{{magicLink}}` | Complete magic link URL with token |\n| `{{token}}` | Raw token value |\n| `{{user.email}}` | User's email address |\n| `{{user.username}}` | User's username |\n| `{{expiresAt}}` | Token expiration time |\n\n**Configuration:**\n1. Create a template in Email Designer\n2. Go to Settings → Magic Link → Email Settings\n3. Enable \"Use Email Designer Template\"\n4. Select your template from the dropdown\n\n### 🛡️ Security Note\n\n**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.\n\n---\n\n## ⚙️ Configuration\n\n### 🔐 Authentication Mode Settings\n\n**Configure in Admin Panel → Settings → MFA \u0026 Login Modes**\n\n```javascript\n{\n  // Authentication Modes (mutually exclusive)\n  \"otp_enabled\": false,            // Enable Email OTP (Premium)\n  \"mfa_require_totp\": false,       // Enable TOTP MFA (Advanced)\n  \"totp_as_primary_auth\": false,   // Enable TOTP-only login (Advanced)\n  \n  // Email OTP Configuration (Premium)\n  \"otp_type\": \"email\",             // \"email\" | \"sms\" | \"totp\"\n  \"otp_length\": 6,                 // Code length (4-8 digits)\n  \"otp_expiry\": 300,               // Validity in seconds (default: 5 min)\n  \"otp_max_attempts\": 3,           // Max verification attempts\n  \"otp_resend_cooldown\": 60,       // Cooldown before resend (seconds)\n  \n  // TOTP Configuration (Advanced)\n  \"totp_issuer\": \"Magic Link\",     // Displayed in authenticator app\n  \"totp_digits\": 6,                // Code digits (6 or 8)\n  \"totp_period\": 30,               // Time period (seconds)\n  \"totp_algorithm\": \"sha1\"         // \"sha1\" | \"sha256\" | \"sha512\"\n}\n```\n\n### ⚙️ General Settings\n\n```javascript\n{\n  \"enabled\": true,                 // Enable/disable plugin\n  \"createUserIfNotExists\": true,   // Auto-create users on first login\n  \"expire_period\": 3600,           // Token expiration (seconds)\n  \"token_length\": 20,              // Token security level (20-40)\n  \"stays_valid\": false,            // Token reusable after first use\n  \"max_login_attempts\": 5,         // Limit failed login attempts\n  \"callback_url\": \"https://yourdomain.com/auth/callback\"\n}\n```\n\n### 📧 Email Settings\n\n```javascript\n{\n  \"from_name\": \"Your App\",\n  \"from_email\": \"noreply@yourdomain.com\",\n  \"response_email\": \"support@yourdomain.com\",\n  \"object\": \"Your Magic Link Login\",\n  \"message_html\": \"\u003ch1\u003eWelcome!\u003c/h1\u003e\u003cp\u003eClick to login: \u003ca href='\u003c%= URL %\u003e?loginToken=\u003c%= CODE %\u003e'\u003eLogin\u003c/a\u003e\u003c/p\u003e\",\n  \"message_text\": \"Your login link: \u003c%= URL %\u003e?loginToken=\u003c%= CODE %\u003e\"\n}\n```\n\n**Template Variables:**\n- `\u003c%= URL %\u003e` - Your confirmation URL\n- `\u003c%= CODE %\u003e` - The generated token\n- `\u003c%= USER %\u003e` - User email/username\n- `\u003c%= EXPIRES_AT %\u003e` - Token expiration time\n\n### 🔒 JWT Settings\n\n```javascript\n{\n  \"use_jwt_token\": true,           // Use JWT for authentication\n  \"jwt_token_expires_in\": \"30d\",   // JWT validity (e.g., '7d', '30d', '365d')\n  \"store_login_info\": true         // Track IP and user agent\n}\n```\n\n### 🛡️ Security \u0026 Rate Limiting\n\n```javascript\n{\n  \"rate_limit_enabled\": true,       // Enable rate limiting\n  \"rate_limit_max_attempts\": 5,     // Max requests per window\n  \"rate_limit_window_minutes\": 15   // Time window in minutes\n}\n```\n\n**How it works:**\n- Limits token requests per IP address\n- Limits token requests per email address\n- Returns `429 Too Many Requests` when exceeded\n- Automatic cleanup every 30 minutes\n- Protects against brute-force and spam attacks\n\n**Example:** With default settings (5 attempts per 15 minutes):\n- User can request max 5 magic links in 15 minutes\n- After 5 attempts, they must wait up to 15 minutes\n- View statistics in Settings → Security \u0026 Rate Limiting\n- Manually cleanup expired entries or reset all limits\n\n---\n\n## 🔑 Required Environment Variables\n\nThe plugin stores secrets that must be protected across restarts and should\nnot be rotated with the rest of your Strapi key material. Two env vars are\n**mandatory in production** (the plugin refuses to boot without them):\n\n```bash\n# 32+ random bytes — used as AES-256 key for TOTP secrets.\n# DO NOT ROTATE: existing TOTP secrets would become undecryptable.\nMAGIC_LINK_ENCRYPTION_KEY=\u003chex from: node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\u003e\n\n# 16+ random bytes — pepper mixed into OTP code hashes to defeat\n# rainbow-table attacks on stored 6-digit codes if the DB is ever leaked.\nMAGIC_LINK_OTP_PEPPER=\u003chex from: node -e \"console.log(require('crypto').randomBytes(24).toString('hex'))\"\u003e\n```\n\nIn non-production environments (`NODE_ENV !== 'production'`) the plugin\nfalls back to derived values so local dev setups keep working — but a WARN\nis logged once per process.\n\n---\n\n## 🌐 Reverse Proxy / Rate-Limit Trust\n\nRate limits and all audit logs identify the caller by `ctx.request.ip`,\nwhich Koa derives from the TCP connection by default. If your Strapi is\nbehind a reverse proxy (nginx, Caddy, Cloudflare, ALB, …) you MUST:\n\n1. Enable proxy trust in `config/server.{js,ts}`:\n\n   ```js\n   module.exports = ({ env }) =\u003e ({\n     proxy: true,   // Trust X-Forwarded-For from the proxy\n     // ... rest of config\n   });\n   ```\n\n2. Configure the proxy to **strip any incoming `X-Forwarded-For`** and\n   emit only its own value. Without this, an attacker can spoof the\n   header, defeating per-IP rate limits.\n\n3. If you have multiple proxy hops, pin trust to the outermost ones via\n   Koa's `app.proxyIpHeader` / `app.maxIpsCount` (see Koa docs).\n\nA misconfigured deployment will either:\n- **Count all requests as the proxy's single IP** (everyone rate-limited together), or\n- **Trust spoofed client IPs** (per-IP rate limit effectively disabled).\n\n---\n\n## 🔐 OTP Strict Binding\n\nStarting with this release, `/otp/send`, `/otp/verify` and `/otp/resend`\nrequire the client to present the `magicLinkToken` they received from\n`/magic-link/login` when it returned `{ requiresOTP: true }`. This prevents\n`/otp/send` from being abused as a standalone email-bombing vector against\narbitrary addresses and prevents `/otp/verify` from being a first-factor\nlogin on its own.\n\nIf you have legacy integrations that must keep the old behaviour, disable\nit via settings: `otp_strict_binding: false`. Doing so emits a WARN on\nevery request and is **not recommended**.\n\n---\n\n## 🎨 Email Templates\n\nCustomize your magic link emails using template variables:\n\n```html\n\u003c!-- HTML Template --\u003e\n\u003ch1\u003eWelcome!\u003c/h1\u003e\n\u003cp\u003eClick to login:\u003c/p\u003e\n\u003ca href=\"\u003c%= URL %\u003e?loginToken=\u003c%= CODE %\u003e\"\u003e\n  Login to Your Account\n\u003c/a\u003e\n```\n\n**Available Variables:**\n- `\u003c%= URL %\u003e` - Your confirmation URL\n- `\u003c%= CODE %\u003e` - The generated token\n\n---\n\n## 🔒 Security Features\n\n- **Token Expiration** - Configurable expiration periods\n- **One-time Tokens** - Optional single-use tokens\n- **IP Tracking** - Monitor login locations\n- **IP Banning** - Block suspicious addresses\n- **JWT Blacklist** - Revoke compromised sessions\n- **Login Attempt Limiting** - Prevent brute force\n- **User Agent Tracking** - Device fingerprinting\n\n---\n\n## 🎯 Use Cases\n\n- **SaaS Applications** - Simplify user onboarding\n- **Customer Portals** - Secure, password-free access\n- **Multi-tenant Systems** - Easy user management\n- **Mobile Apps** - Seamless authentication flow\n- **Content Platforms** - Reduce password fatigue\n\n---\n\n## 🐛 Troubleshooting\n\n### Common Issues\n\n| Issue | Solution |\n|-------|----------|\n| **Emails not sending** | Check Strapi email provider configuration in `config/plugins.js` |\n| **Token invalid errors** | Verify token hasn't expired. Check `expire_period` setting |\n| **User not found** | Enable `createUserIfNotExists` setting in admin panel |\n| **License activation fails** | Check network connectivity. Try manual activation with license key |\n| **npm install fails** | Use `npm install --legacy-peer-deps` or update to latest npm |\n| **OTP settings not visible** | Check license tier. Email OTP requires Premium license |\n| **TOTP not working** | Verify Advanced license is active. Check authenticator app time sync |\n| **\"Current License: Free\" but Premium activated** | Refresh browser cache. License info loads from `/magic-link/license/status` |\n| **Lock icons not centered** | Update to v4.16.1+. Mobile UI fixes are included |\n| **Email OTP shown without license** | Update to v4.16.3+. License validation is fixed |\n\n### Debug Mode\n\nEnable debug logging in your Strapi instance:\n\n```bash\n# In development\nNODE_ENV=development npm run develop\n\n# Check logs in terminal for Magic Link debug output\n```\n\n### Reset Plugin\n\nIf you need to reset the plugin:\n\n```bash\n# 1. Stop Strapi\n# 2. Clear plugin database entries\n# 3. Restart Strapi\nnpm run develop\n```\n\n### Get Support\n\n- 🐛 [GitHub Issues](https://github.com/Schero94/strapi-plugin-magic-link-v5/issues)\n- 📖 Check the README for updated examples\n\n---\n\n## 🤝 Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'feat: add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n**Commit Convention:** Follow [Conventional Commits](https://www.conventionalcommits.org/)\n- `feat:` - New features\n- `fix:` - Bug fixes\n- `docs:` - Documentation changes\n- `chore:` - Maintenance tasks\n\n---\n\n## Changelog\n\n### v5.2.7 (2025-12-16) - Latest\n\n**WhatsApp Integration**\n- **WhatsApp Magic Links** - Send authentication links via WhatsApp messenger (FREE!)\n- **Customizable Templates** - Configure WhatsApp message text with variables\n- **Frontend URL Setting** - Set custom frontend login URLs for magic links\n- **Magic-Mail Integration** - Automatic delegation when both plugins installed\n- **QR Code Setup** - Easy WhatsApp connection via QR code scan\n- **Debug Mode** - Optional verbose logging for WhatsApp connections\n\n**New Settings:**\n- `frontend_login_url` - Custom frontend URL for magic links\n- `whatsapp_message_text` - WhatsApp message template\n- `whatsapp_app_name` - App name displayed in WhatsApp messages\n- `whatsapp_debug` - Enable/disable verbose WhatsApp logging\n\n### v5.1.10 (2025-12-15)\n\n**Plugin Compatibility Mode**\n- **Passwordless Plugin Compatibility** - Migration support for `strapi-plugin-passwordless`\n  - Added `/api/passwordless/send-link` and `/api/passwordless/login` routes\n  - Identical request/response format - no frontend changes needed!\n  - Disabled by default for security (returns 404 when off)\n- **Email Designer 5 Integration** - Enhanced template support\n- New \"Compatibility\" settings section in admin UI\n- Full i18n support for compatibility settings (DE, EN, ES, FR, PT)\n- `compatibility-check` policy for secure route access\n\n### v4.16.3 (2025-11-22)\n\n**License Validation Fix**\n- ✅ Fixed license tier detection from backend features\n- ✅ Correctly extracts `premium`, `advanced`, `enterprise` from license response\n- ✅ Enriched license info with `tier` field for UI compatibility\n- 🐛 Fixed \"Current License: Free\" display bug when Premium is active\n\n### v4.16.2 (2025-11-22)\n\n**Active State \u0026 Auto-Disable Fix**\n- ✅ \"Magic Link Only\" now always shows ACTIVE with FREE license\n- ✅ Email OTP settings only visible with Premium+ license\n- ✅ Auto-disable premium features when license doesn't support them\n- ✅ Added useEffect hook for automatic license enforcement\n\n### v4.16.1 (2025-11-22)\n\n**Lock Icons \u0026 License Checks**\n- ✅ Perfectly centered lock/lightning icons in locked mode cards\n- ✅ Fixed license validation in active state detection\n- ✅ Locked features now show proper disabled state\n- 🎨 Improved locked overlay with flexbox centering\n\n### v4.16.0 (2025-11-22)\n\n**Major UX Improvements**\n- 🎨 Complete MFA settings redesign with modern UI\n- 💎 License tier status display with visual indicators\n- 🔒 Locked feature cards with clear premium/advanced badges\n- 🇩🇪 Full German translations for all MFA settings\n- ✅ Improved explanations and help texts\n- 📧 Better Email OTP configuration section\n- 🔐 Added step-by-step \"How it works\" guides\n\n### v4.15.6 (2025-11-22)\n\n**Email OTP Configuration**\n- ✅ Added Email OTP configuration section\n- ⚙️ Code length, expiry time, max attempts settings\n- 🔄 Resend cooldown configuration\n- 📧 OTP delivery method selection\n- ℹ️ Improved help text and info boxes\n\n### v4.15.5 (2025-11-22)\n\n**Mobile \u0026 Settings UI Fixes**\n- 📱 Fixed mobile button icon centering issues\n- 🗑️ Removed duplicate OTP accordion\n- 🎨 Improved badge colors for better readability\n- ✨ Better contrast for Premium/Advanced badges\n\nFor full version history, see [GitHub Releases](https://github.com/Schero94/strapi-plugin-magic-link-v5/releases).\n\n---\n\n## 📄 License\n\nThis project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.\n\n**Important:** While the code is open source, the license validation system must remain intact. This ensures quality, security, and continued development of the plugin.\n\n---\n\n## 💬 Support \u0026 Community\n\n### Need Help?\n\n- 🐛 **Bug Reports**: [GitHub Issues](https://github.com/Schero94/strapi-plugin-magic-link-v5/issues)\n- 💡 **Feature Requests**: [GitHub Discussions](https://github.com/Schero94/strapi-plugin-magic-link-v5/discussions)\n- 📦 **npm Package**: [strapi-plugin-magic-link-v5](https://www.npmjs.com/package/strapi-plugin-magic-link-v5)\n\n### Resources\n\n- 📖 **Documentation**: This README + [Strapi Docs](https://docs.strapi.io)\n- 💻 **Source Code**: [GitHub Repository](https://github.com/Schero94/strapi-plugin-magic-link-v5)\n- 🎯 **Live Example**: Coming soon\n- 📹 **Video Tutorial**: Coming soon\n\n### Sponsoring\n\nIf this plugin saves you time and you'd like to support development:\n\n- ⭐ Star the repository on GitHub\n- 🐛 Report bugs and suggest features\n- 💡 Contribute code improvements\n- 📢 Share with the Strapi community\n\n---\n\n## Security Advisories \u0026 Dependency Overrides\n\nThis plugin ships with a first-party dependency tree audited on every\nrelease, but two transitive paths are governed by upstream packages we\ndo not control. If your own `npm audit` flags any of the following,\nadd the matching entry to your **Strapi project's** `package.json`\n(not this plugin's — npm `overrides` only take effect on the\nroot project):\n\n```jsonc\n{\n  \"overrides\": {\n    // GHSA-xq3m-2v4x-88gg — protobufjs \u003c7.5.5 arbitrary code execution.\n    // Reaches us via @whiskeysockets/baileys → libsignal → protobufjs.\n    // We already ship this override for local development; apply it in\n    // your Strapi project so the fix propagates to the installed tree.\n    \"protobufjs\": \"^7.5.5\",\n\n    // Optional: if you do not use the WhatsApp delivery feature, you\n    // can also skip installing @whiskeysockets/baileys altogether:\n    //   npm install --omit=optional\n    // (The Magic-Link code path detects the missing module and\n    // disables the WhatsApp controller at runtime — the rest of the\n    // plugin keeps working.)\n  }\n}\n```\n\nAfter editing `package.json`, run:\n\n```bash\nrm -rf node_modules package-lock.json\nnpm install\nnpm audit\n```\n\nThe critical `protobufjs` advisory disappears once the override is in\nplace. The remaining `@strapi/design-system → lodash` advisory is a\nStrapi-core issue and will clear up automatically when you upgrade\n`@strapi/strapi` to the latest patch release.\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**Made with ❤️ by [Schero94](https://github.com/Schero94)**\n\n*Passwordless authentication, simplified.*\n\n[![GitHub](https://img.shields.io/github/stars/Schero94/strapi-plugin-magic-link-v5?style=social)](https://github.com/Schero94/strapi-plugin-magic-link-v5)\n[![npm](https://img.shields.io/npm/dt/strapi-plugin-magic-link-v5)](https://www.npmjs.com/package/strapi-plugin-magic-link-v5)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschero94%2Fstrapi-plugin-magic-link-v5","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fschero94%2Fstrapi-plugin-magic-link-v5","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschero94%2Fstrapi-plugin-magic-link-v5/lists"}