REP Magic Login (Passwordless Email Link)
Passwordless WordPress login via one-time emailed magic links. Single-use, hashed, expiring tokens; intentionally bypasses 2FA by design.
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/abchiaravalle/rep-magic-login/archive/refs/heads/main.zipReadme
REP Magic Login (Passwordless Email Link)
A single-file WordPress plugin that lets users log in via a one-time, short-lived link emailed to their on-file address — no password required. Built for users who struggle with passwords.
How it works
- On
wp-login.php, a user clicks "Can't use your password? Email me a login link". - They enter their username or email. A 256-bit token is generated; only its SHA-256 hash is stored (
update_user_meta). The raw token is emailed as a link to the account's on-file address — never to whatever address they type in the form. - Clicking the link opens a small confirm page that auto-submits a POST to actually authenticate. The token is deleted from the database before validation (so it can never be replayed), then compared with
hash_equals()(constant-time), then the user is logged in viawp_set_auth_cookie(). - The link works once and expires in 15 minutes.
Security design
- 256-bit tokens, only SHA-256 hashes ever touch the database.
- Single-use: the token record is deleted before validation, so replay is impossible even if the link leaks after use.
- 15-minute expiry.
- Two-step consume (GET renders, POST authenticates): the emailed link is a plain GET, but visiting it does not consume the token — it renders a page that auto-submits a POST. This exists specifically so automated email-security link scanners (Microsoft 365 Safe Links, Proofpoint, Mimecast, etc.), which prefetch links server-side before a human opens them, can't silently burn the token.
- No user enumeration: the request-a-link response is identical (body and, as of v1.1.0, padded timing) whether or not the submitted username/email resolves to a real account.
- Per-user AND per-IP rate limiting: 1 request/min per resolved account, plus an IP-level cap so guessing usernames that don't exist is still throttled.
- CSRF protection via WordPress nonces on the request form.
- Failed/expired/invalid token attempts fire
wp_login_failed, so security plugins (Wordfence, etc.) can see and alert on them. - Administrators are excluded from magic login by default (filterable) — see the intentional trade-off below.
Intentional trade-off: this bypasses 2FA
Authentication happens via wp_set_current_user() / wp_set_auth_cookie() directly rather than through wp_signon()/wp_authenticate, which means any 2FA plugin's normal auth-chain hooks are not invoked. This is deliberate: for the target audience — people who struggle with passwords — possession of the emailed link is the second factor. If you need 2FA enforced even for magic-login users, don't enable this plugin for those accounts, or use the rep_magic_login_allowed_user filter to exclude them.
Filters
// Control which users may use magic login. Receives (bool $allowed, WP_User $user).
// Default: everyone except administrators.
add_filter( 'rep_magic_login_allowed_user', function( $allowed, $user ) {
return $allowed;
}, 10, 2 );
// Control the post-login redirect. Receives (string $url, WP_User $user).
add_filter( 'rep_magic_login_redirect', function( $url, $user ) {
return $url;
}, 10, 2 );
Requirements
- WordPress with a working outbound mailer (e.g. WP Mail SMTP) — magic links are useless if
wp_mail()can't actually deliver. - PHP
random_bytes()(PHP 7+, built in).
Installation
Drop rep-magic-login.php into wp-content/plugins/rep-magic-login/ and activate it from the WordPress admin, or via WP-CLI:
wp plugin activate rep-magic-login
Known limitations
- Corporate email link-scanners that execute JavaScript (rare, but exists) could still trigger the auto-submit on the confirm page. The two-step design blocks the common case (server-side prefetchers that fetch the URL without rendering/executing anything), not every conceivable scanner.
- Timing-based padding on the "request a link" response reduces but does not perfectly eliminate a timing side-channel for enumeration on unusually slow mail infrastructure.
- No built-in CAPTCHA/honeypot on the request form; the per-IP rate limit is the primary defense against automated abuse at volume.
License
GPL-2.0-or-later