Skip to content

Feature: optional pretty /login URL for the branded login screen #9

@r00bbert

Description

@r00bbert

Summary

Add an admin toggle that, when enabled, makes the branded login screen also reachable at the pretty URL /login — in addition to its normal home at wp-login.php. Think of it as a minimal version of the "custom login URL" feature other login plugins offer.

Explicitly in scope / out of scope for this request:

  • Links and redirects can keep pointing at wp-login.php — that's fine. The /login URL only needs to also render the branded login screen.
  • This is not about hiding/moving wp-login.php (no security-through-obscurity). Both URLs serve the form; /login is just a friendlier entry point.

Motivation

/login is a memorable, conventional URL. Site owners frequently expect it to work and it reads better in docs, emails, and onboarding than wp-login.php.

Proposed behaviour

  • New boolean setting in magicauth_settings (e.g. enable_login_url), default off.
  • When on: a request to /login (and /login/) renders the branded login screen — the same shell/branding as the ?action=magicauth screen on wp-login.php.
  • When off: /login behaves exactly as it does today (404 or whatever the site already serves).

Feasibility — research done

I ran exploration over the current codebase. Verdict: moderate — no architectural blockers, no changes to the security model. Estimated ~75 LOC across 4 files.

How the login screen works today

  • Frontend\LoginScreen hooks wp-login.php exclusively via login_init, login_form_login, login_form_magicauth, etc. (includes/Frontend/LoginScreen.php).
  • It does not register any rewrite rules, query vars, or template_redirect hooks today. The only init-priority-1 hook is Auth\Controller for ?magicauth=verify.
  • [magicauth_login] shortcode already renders the form on an arbitrary page, but without the full branded shell — so /login should reuse LoginScreen's render path, not the shortcode's.

Recommended approach

  1. Register a rewrite rule add_rewrite_rule('login/?$', 'index.php?magicauth=login_route', 'top') so WP doesn't 404.
  2. Add a template_redirect handler (priority ~5) in LoginScreen that detects the /login route, emits security headers, renders the branded shell, and exits.
  3. flush_rewrite_rules(false) on activation/deactivation in includes/Installer.php.
  4. Standard settings plumbing for the new toggle.

Files that would change

  • includes/Frontend/LoginScreen.phptemplate_redirect hook + render_login_route() method (~40 LOC)
  • includes/Installer.php — rewrite rule register/flush on activate/deactivate + slug-collision check (~20 LOC)
  • includes/Admin/Settings.php — new field renderer, section wiring, sanitize branch (~15 LOC)
  • includes/helpers.php — add the setting to defaults (~1 LOC)
  • _dev/decisions.md — document the decision

Conflict analysis

Conflict Severity Mitigation
Existing Page/Post with slug login collides with the rewrite rule HIGH Detect at activation via get_page_by_path('login'); surface an admin notice instead of silently shadowing the post. Consider also refusing to enable the toggle while a login post exists.
Security headers (X-Frame-Options: DENY, CSP: frame-ancestors 'none', X-Content-Type-Options: nosniff) are emitted in login_init — that hook does not fire on a front-end route HIGH The template_redirect handler must re-emit the exact same headers before rendering. Easy to forget — call it out in code review and tests.
Rewrite rule not flushed MEDIUM Flush on activate/deactivate. Without it, /login 404s until someone re-saves permalinks.
?magicauth=off opt-out cookie is path-scoped to /wp-login.php, so it won't be read on /login LOW Recovery stack is not broken — the always-visible "Sign in with password" link and MAGICAUTH_DISABLE constant still work. Just document that the cookie opt-out is wp-login.php-scoped.
Multisite subdirectory behaviour of a /login rule NOTE ONLY Multisite is out of scope for v1.0; document for later.
Throttling, user-enumeration timing jitter, TOCTOU token consumption NONE All route-agnostic — throttling is keyed on IP/email hashes, jitter is applied at response time, token consumption is atomic. Serving the form at a second URL changes none of this.

Acceptance criteria

  • New enable_login_url toggle in Settings, default off.
  • When on, /login and /login/ render the branded login screen with full branding shell.
  • When off, /login is untouched (no rewrite rule active).
  • /login response carries the same security headers as wp-login.php (X-Frame-Options, CSP frame-ancestors, X-Content-Type-Options).
  • Rewrite rules flushed on activate/deactivate.
  • Activation detects an existing login post/page and warns rather than silently shadowing it.
  • Throttling and recovery (?magicauth=off link, password link) verified to still work when reaching the form via /login.

Researched and filed by @r00bbert. Feature scoped as minimal — both URLs serve the form, no wp-login.php hiding.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions