Passwordless WordPress sign-in via email magic link or typeable 6-character code.
One email, two ways in: click the link on the device that has the email, or type the 6-character code on the device you're trying to sign in on. Cross-device flows just work.
MagicAuth is a focused, single-purpose authentication plugin. No SMS, no QR codes, no third-party SSO, no SaaS subscription, no telemetry. The whole plugin is one moving part: send the user a sign-in email, let them in.
- One email, two methods. Every sign-in email carries both a clickable magic link and a typeable Crockford-base32 6-character code. Users pick whichever works for the device they're on.
- Branded login screen. Optional drop-in replacement for
wp-login.phpwith your logo and brand color. Toggleable from settings. [magicauth_login]shortcode. Embed the sign-in form on any page or post. Works inside any theme.- Throttling, on by default. Per-IP, per-email, and per-row rate limits. No admin opt-in required and no admin override path.
- Three-layer recovery. Always-visible "Sign in with password" link,
?magicauth=offURL parameter, andMAGICAUTH_DISABLEPHP constant. No admin can get locked out, ever. - Admin user-profile integration. Issue, send, and reset magic links from the user-edit screen. Per-user disable for accounts that should never receive sign-in emails.
- WP privacy exporter and eraser. Honors WordPress's built-in personal-data export and erasure tools out of the box.
- 256-bit tokens.
bin2hex(random_bytes(32)). Neverwp_rand,wp_generate_password, orsha1. - HMAC-stored verifiers. Plaintext exists only in the URL or email. The database stores
hash_hmac('sha256', $plaintext, wp_salt('auth')). Comparisons run throughhash_equals(). - Opaque selectors. URLs never carry a
user_id. Lookups use a randomly-generated selector. - Atomic consume. Token consumption is a single
UPDATE ... WHERE consumed_at IS NULLgated on the row state. Auth cookies are set only after the row is provably consumed; TOCTOU is closed. - No enumeration oracle. All response paths emit a uniform generic error and a 50 to 150 ms timing jitter. No admin override.
- HMAC-truncated IPs. IPs are stored only as a 16-character HMAC. Source is
$_SERVER['REMOTE_ADDR']; neverX-Forwarded-FororCF-Connecting-IP. - Weak-salts detection. On activation, MagicAuth checks
wp-config.phpfor placeholder salts. Branded login replacement refuses to enable until salts are set.
From WordPress.org: coming soon at https://wordpress.org/plugins/magicauth/ (pending review).
Manually:
- Download the latest release from Releases.
- WP Admin > Plugins > Add New > Upload Plugin > upload the zip > Activate.
- Visit Settings > MagicAuth to set your logo, brand color, and behavior.
- Drop
[magicauth_login]on a page, or toggle "Replace WordPress login screen" to take overwp-login.php.
If MagicAuth is misbehaving, three independent escape hatches are wired up by default:
- The "Sign in with password" link on every MagicAuth login form. Bytes-identical for every visitor (no email lookup, no role check) so it cannot be used for user enumeration.
- Append
?magicauth=offtowp-login.phpto fall back to the native WordPress login for the current session. - Add
define('MAGICAUTH_DISABLE', true);towp-config.php. Disables MagicAuth entirely, plugin keeps its data, no uninstall required.
- PHP 8.0+ (strict types throughout)
- WordPress 6.4+
- No vendored runtime dependencies. Composer is dev-only (PHPStan, PHPUnit, WP coding standards).
- Vanilla JS, no jQuery on the front end. Plain CSS scoped to
.magicauth-. - No external CSS frameworks, no build step, no Node toolchain required.
git clone https://github.com/EtticDevelopment/magicauth.git
cd magicauth
# Install dev dependencies (PHPStan, PHPUnit, etc.)
composer install
# Symlink into a local WordPress install
ln -s "$(pwd)" /path/to/wordpress/wp-content/plugins/magicauth
# Activate via WP-CLI
wp plugin activate magicauth --path=/path/to/wordpresscomposer testThe model-layer tests use a self-contained PHPUnit bootstrap with a SQLite-backed $wpdb shim, so atomic UPDATE-with-WHERE semantics are tested against a real database, not mocks.
composer phpstanwp plugin check magicauth \
--categories=plugin_repo,security,performance,general,accessibility \
--severity=warningShould report "No errors found."
MagicAuth aims to do one thing well: passwordless sign-in for a single WordPress site. To keep the surface area small and the security model easy to reason about, the following are intentionally not included in v1.0:
- SMS, phone OTP, QR codes — email-based auth only.
- User registration — MagicAuth signs existing users in; account creation stays with WordPress core or your registration plugin of choice.
- Third-party integrations (WooCommerce, Easy Digital Downloads, Elementor, FluentCRM) — MagicAuth works with any plugin that uses the standard WordPress login, but ships no bespoke integrations.
- CAPTCHA providers (reCAPTCHA, hCaptcha, Turnstile) — built-in throttling handles abuse; pair with a dedicated CAPTCHA plugin if you need more.
- REST API endpoints, WP-CLI commands, Gutenberg block — deferred; the shortcode and
wp-login.phpreplacement cover v1.0 use cases. - Multisite network mode — single-site only for now.
- Audit log / event-stream export — out of scope for v1.0.
- Telemetry, analytics, license checks — zero outbound HTTP from the plugin, and that's a permanent design choice.
Some of these may land in a future version; others (telemetry, phone-based auth) won't. If you're working on a contribution that touches one of these areas, open an issue first so we can talk through fit before you invest the time.
Issues and pull requests welcome. Before opening a PR:
- Run the test suite (
composer test). Should pass. - Run PHPStan (
composer phpstan). Should be clean at level 6. - Run Plugin Check (above). Should report zero errors.
- Keep PHP 8.0 as the floor.
- If you're adding a user-facing string, wrap it in the
magicauthtext domain.
1.0.0 is the first public release.
GPL-2.0-or-later. Same as WordPress core.
Built and maintained by Ettic.