WP Manifestindependent plugin directory
manifest / email / email-only-otp-login-wordpress-plugin

Email Only OTP Login

Replaces the default WordPress login with a username-only + email OTP flow. Configurable via Settings → OTP Login.

by Edwin Krisnha · github.com/edwinkrisnha/email-only-otp-login-wordpress-plugin · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/edwinkrisnha/email-only-otp-login-wordpress-plugin/archive/refs/heads/master.zip

GitHub

A WordPress plugin that replaces the default password-based login with a secure email OTP (One-Time Password) flow.

Features

  • OTP login flow — Users enter their username or email, receive a time-limited numeric code via email, then enter the code to log in.
  • Magic link login — Optionally send a one-click login URL instead of (or alongside) a numeric code.
  • Resend cooldown — A "Resend code" button with a configurable cooldown prevents email spam.
  • Email-only mode — Optionally restrict the login field to email addresses only (no usernames).
  • Block password login — Disable password-based authentication entirely, forcing OTP-only login.
  • Domain allow-list — Restrict login and registration to specific email domains.
  • Rate limiting — Limit OTP requests per IP to prevent abuse.
  • Max OTP attempts — Invalidate codes after a configurable number of wrong guesses.
  • XML-RPC protection — Optionally disable XML-RPC, which requires password auth and bypasses OTP.
  • REST API protection — Block Authorization-header-based auth (Basic Auth, Application Passwords) when password login is disabled.
  • Login attempt log — Admin table showing every login event: timestamp, identifier, IP, event type, and resolved user.
  • HTML email template — Emails are sent as styled HTML (with a plain-text fallback) using a built-in minimal template. The OTP appears in a prominent code block; magic-link login renders a clickable button.
  • Test email tool — Send a preview OTP email from the admin panel to verify your mail configuration.

Requirements

  • WordPress 5.6+
  • PHP 7.4+
  • A working wp_mail() configuration (SMTP plugin recommended)

Installation

  1. Upload the email-only-otp-login folder to /wp-content/plugins/.
  2. Activate the plugin from Plugins → Installed Plugins.
  3. Configure via Settings → OTP Login.

Configuration

Navigate to Settings → OTP Login in the WordPress admin.

General

Setting Default Description
OTP Length 6 Number of digits in the one-time code (4–10).
OTP Expiry 10 min How long a code or magic link remains valid (1–60 min).
Block Password Login On Disables password-based authentication. Uncheck for WP-CLI or admin recovery.
Email-Only Login Off When on, the login field only accepts email addresses.
Login Method OTP code only Controls what is sent in the email: OTP code, magic link, or both.
Resend Cooldown 60 s Seconds a user must wait before using the Resend button (30–300).

Security

Setting Default Description
Max OTP Attempts 3 Wrong guesses allowed before the code is invalidated (1–10).
Rate Limit 5 requests Max OTP requests per IP within the rate limit window (1–20).
Rate Limit Window 15 min Time window for rate limiting (1–60 min).
Trust Proxy Headers Off Enable only when the site sits behind a trusted reverse proxy or Cloudflare. When off (default), REMOTE_ADDR is used for rate limiting, which cannot be spoofed. Enabling without a real proxy allows attackers to bypass rate limits by sending arbitrary X-Forwarded-For / CF-Connecting-IP headers.
Disable XML-RPC On Recommended — XML-RPC requires password auth and bypasses OTP.

Login Log

Navigate to Settings → OTP Login Log to view recent login activity. Also accessible via the "Log" shortcut on the Plugins list row.

Column Description
Date / Time Timestamp in the site's configured timezone.
Identifier Username or email address that was submitted.
IP Address Client IP at the time of the attempt.
Event Colour-coded badge (see table below).
User Resolved WordPress display name and username, if known.

Event types:

Event Meaning
OTP sent OTP or magic link email was sent successfully.
OTP verified Correct code entered — login completed.
Magic link used Magic link clicked — login completed.
OTP failed Wrong code entered (attempts remaining).
OTP expired Token expired before a correct code was entered.
Attempts exhausted Max wrong guesses reached — token invalidated.
Magic link expired Magic link clicked after it expired.
Rate limited Request blocked by the IP rate limiter.
Password blocked Password login attempted while it is disabled.

Log Retention (Settings → OTP Login → Login Log section): configure how many days to keep entries (default 30). Old entries are pruned automatically once per day.

Allowed Email Domains

Enter one domain per line (e.g. example.com). Leave blank to allow all domains. Users whose email does not match will see a generic "code sent" message (no enumeration).

Email Template

OTP emails are sent as HTML using the built-in styled template (templates/email-otp.php). The HTML version automatically shows or hides the OTP code block and magic-link button based on your Login Method setting.

The Subject and Plain-text fallback fields are customizable in Settings → OTP Login. The plain-text fallback is used by email clients that cannot render HTML. Available placeholders for both fields:

Customizing the HTML template

Theme override — Copy the built-in template into your theme and edit it freely. Plugin updates will never overwrite a theme file:

wp-content/themes/your-theme/email-only-otp-login/email-otp.php

The following PHP variables are available inside the template:

Variable Type Description
$site_name string WordPress site name
$display_name string User's display name
$otp string One-time code (empty string when method is magic)
$magic_url string Magic-link URL (empty string when method is otp)
$expiry_minutes int Expiry in minutes
$login_method string otp, magic, or both

Filter hook — Replace or post-process the HTML body entirely in code (e.g. in functions.php or a mu-plugin):

add_filter( 'otp_login_email_html', function ( $html, $user, $otp, $magic_url, $s ) {
    // Return your own HTML string.
    return $html;
}, 10, 5 );
Placeholder Value
{site_name} Your WordPress site name
{display_name} The user's display name
{otp} The one-time code (empty when Login Method is "Magic link only")
{magic_link} The one-click login URL (empty when Login Method is "OTP code only")
{expiry_minutes} Code/link expiry in minutes

Tip: Include {magic_link} in the plain-text fallback when Login Method is "Magic link" or "Both".

Login Methods

Method Behavior
OTP code only User enters a numeric code on the login page (default).
Magic link only User clicks a one-click URL in the email — no code entry needed.
Both Email contains both the code and the link; user can use either.

Magic links are single-use and expire after the same duration as OTP codes. Clicking a magic link shows a confirmation page first; the token is only consumed when the user clicks the Log In button. This prevents email security scanners (Microsoft Defender SafeLinks, Proofpoint, etc.) from inadvertently consuming the token before the user sees it.

File Structure

email-only-otp-login/
├── email-only-otp-login.php   # Plugin bootstrap
├── includes/
│   ├── settings.php           # Admin settings, field renderers
│   ├── otp.php                # Login flow, OTP helpers, email, render forms
│   ├── security.php           # Rate limiting, XML-RPC, REST API protection
│   └── log.php                # Login attempt log (DB table, admin page)
├── templates/
│   └── email-otp.php          # HTML email template (OTP code block + magic link)
├── CHANGELOG.md
├── LICENSE
└── README.md

Security Notes

  • OTP codes are hashed with wp_hash_password() before storage.
  • Tokens are stored as WordPress transients and deleted immediately after use or expiry.
  • Magic links are two-phase: the token is only consumed when the user clicks Log In on the confirmation page, not on the initial GET request. This prevents email security scanners from consuming tokens before the user clicks.
  • OTP attempt counting uses an atomic delete-then-reissue pattern to prevent concurrent submissions from bypassing the max-attempts limit.
  • Rate limiting uses REMOTE_ADDR (the real connection IP) by default, which cannot be spoofed. Proxy header trust must be explicitly opt-in via the Trust Proxy Headers setting.
  • User enumeration is prevented — invalid usernames, blocked domains, and non-existent accounts all produce the same generic response.
  • Rate limiting counters are stored as transients, keyed by a hash of the client IP.
  • Resend cooldowns are enforced server-side per user, independent of the client-side countdown.
  • Login events are stored in a dedicated {prefix}otp_login_log database table with timestamps in UTC.

License

GPLv2 or later. See LICENSE.