A lightweight WordPress plugin that provides a front-end login popup triggered by URL parameter. Built with modern PHP 7.4+ standards, vanilla JavaScript, and clean architecture.
- Lightweight - Minimal footprint with clean, organized code structure
- Pure JavaScript - No jQuery or other libraries required
- Cache Compatible - Works perfectly with all page caching solutions (WP Rocket, LiteSpeed, etc.)
- Turnstile Integration - Automatic integration with Simple Cloudflare Turnstile plugin for CAPTCHA protection
- Two-Factor Authentication - Automatic integration with Two Factor plugin with seamless redirect flow
- Secure - Rate limiting, nonce verification, username enumeration prevention, and proper sanitization
- Fast UX - Instant loading spinner with smooth transitions
- Accessible - WCAG compliant with proper ARIA attributes
- Extensible - Multiple hooks for custom authentication flows and customization
- Translation Ready - Fully internationalized with i18n support
- Well Architected - Proper MVC separation with dedicated classes for each concern
- Multiple URL Triggers - Supports
?sl=true,?sl=1,?show_login=true, and?show_login=1
- WordPress 5.0 or higher
- PHP 7.4 or higher
- Download the plugin files
- Upload the
show-logindirectory to/wp-content/plugins/ - Activate the plugin through the WordPress admin
- The plugin works automatically when the URL parameter is present
Add any of these URL parameters to trigger the login popup for non-logged-in users:
https://example.com/page?sl=true
https://example.com/page?sl=1
https://example.com/page?show_login=true
https://example.com/page?show_login=1
All four parameter variations work identically. Parameters can be combined with other URL parameters:
https://example.com/shop/product?id=123&sl=true
https://example.com/members/content?category=premium&show_login=1
When a user successfully logs in:
- The page reloads with them logged in
- All URL parameters are preserved
- Only the popup trigger parameter is removed (e.g.,
slorshow_login)
- User visits a page with a popup trigger parameter (e.g.,
?sl=true) - Popup appears instantly with loading spinner ("Checking login status...")
- Login status checked via AJAX (bypasses page cache)
- If logged out: Shows "You're not logged in" (1 second) → Login form appears
- If logged in: Shows "You're already logged in!" (1 second) → Popup closes
- User can close popup via:
- X button in top-right corner
- Clicking outside popup on overlay
- Pressing ESC key
- User enters credentials and submits
- AJAX authentication happens without page reload
- Errors display in popup without disruption
- On success, page reloads and user is logged in
Built-in IP-based rate limiting prevents brute-force attacks:
- Default: 5 attempts per 1 minute
- Uses WordPress transients for storage
- Clears on successful login
- Supports proxy/CDN headers (Cloudflare, X-Forwarded-For, etc.)
- Shows dynamic countdown in error message
Customize via filters:
// Change max attempts
add_filter('show_login_max_attempts', function($max) {
return 10; // Allow 10 attempts
});
// Change time window (in seconds)
add_filter('show_login_rate_limit_window', function($window) {
return 3600; // 1 hour window (default: 60 = 1 minute)
});All AJAX requests are protected with WordPress nonces using check_ajax_referer().
- Username:
sanitize_text_field()+wp_unslash() - Password:
wp_unslash()only (no sanitization to preserve special characters) - Remember:
sanitize_text_field()+ boolean check - URLs:
esc_url_raw()to preserve structure
Error messages are sanitized to prevent username enumeration attacks:
- Invalid username/email and incorrect password both return: "Invalid username or password"
- Specific error codes (
invalid_username,invalid_email,incorrect_password) are replaced with generic messages - Prevents attackers from determining which usernames exist on the site
The plugin is fully compatible with all page caching solutions:
- JavaScript checks
?sl=trueparameter client-side before any AJAX calls - Login status checked via AJAX (always executes fresh PHP with user cookies)
- Nonces generated fresh per user - Created dynamically via AJAX, never cached
- Redirect URLs computed per request - Always accurate, never stale
- Works with WP Rocket, W3 Total Cache, Cloudflare, and all other caching plugins
- Instant loading spinner with status messages provides clear feedback:
- "Checking login status..." (during AJAX)
- "You're not logged in" (if logged out, shows 1 second before form)
- "You're already logged in!" (if logged in, shows 1 second before closing)
Show Login includes built-in integration with the Simple Cloudflare Turnstile plugin for CAPTCHA protection.
- ✅ Automatic Detection - Activates only when Turnstile plugin is installed and configured
- ✅ Cache Compatible - Turnstile scripts load dynamically via JavaScript
- ✅ All Settings Supported - Works with all Turnstile themes, sizes, and appearance modes
- ✅ Whitelist Support - Respects Turnstile's IP/user whitelist settings
- ✅ Button Disable Support - Compatible with "disable submit button" option
- ✅ Zero Configuration - No setup needed beyond installing both plugins
- Install and activate Simple Cloudflare Turnstile
- Configure your Cloudflare Turnstile Site Key and Secret Key in Settings > Turnstile
- That's it! The integration works automatically
Note: You do NOT need to enable the "Login" checkbox in Turnstile settings - Show Login popup works independently with its own integration.
The integration works with ALL Turnstile settings:
- ✅ Appearance Modes: Always visible, Execute (invisible), or Interaction-only
- ✅ Themes: Light, Dark, or Auto
- ✅ Sizes: Normal, Compact, or Flexible
- ✅ Disable Submit Button: Supported in both visible and invisible modes
- ✅ Languages: All Cloudflare Turnstile languages
- ✅ Whitelist: IP and user whitelisting honored
- ✅ Custom Error Messages: Displayed in popup on validation failure
When both plugins are active and Turnstile is configured:
- User visits page with popup trigger parameter
- Turnstile API loads dynamically (cache-compatible)
- Login popup appears with Turnstile widget between "Remember Me" and submit button
- User interaction:
- Visible mode: User completes the visible challenge, then submits
- Invisible mode: User fills form and clicks submit, verification happens automatically
- Turnstile token (
cf-turnstile-response) sent with login request - Server validates token before authentication via
cfturnstile_check() - If validation fails, user sees error message (customizable in Turnstile settings)
- If validation succeeds, normal authentication proceeds
Invisible Mode Behavior:
- No visible widget shown to user
- Verification runs automatically when submit button is clicked
- Seamless UX - users don't see CAPTCHA unless flagged as suspicious
- All validation happens server-side - no difference in backend flow
Technical Note - Single-Use Token Handling:
Cloudflare Turnstile tokens are single-use only. Show Login automatically prevents double validation by skipping the Turnstile plugin's default WordPress login check (via the cfturnstile_wp_login_checks filter) when handling Show Login AJAX requests. This ensures:
- Only ONE validation occurs per token
- Show Login popup validation works correctly
- Standard WordPress login form (
wp-login.php) continues to work with Turnstile - No conflicts between the two integrations
// Skip Turnstile validation for specific users/conditions
add_filter('show_login_skip_turnstile', function($skip) {
// Example: Skip for admin users testing
if (current_user_can('manage_options')) {
return true;
}
return $skip;
});// Track Turnstile failures
add_action('cfturnstile_show_login_failed', function($username) {
error_log("Turnstile validation failed for user: {$username}");
});
// Track Turnstile successes
add_action('cfturnstile_show_login_success', function($username) {
error_log("Turnstile validation passed for user: {$username}");
});Cache Compatibility:
- Turnstile scripts load dynamically via JavaScript when popup trigger parameter is detected
- Works with all caching plugins (WP Rocket, LiteSpeed Cache, W3 Total Cache, etc.)
- No cache variations needed for URL parameters
Validation:
- Server-side validation occurs before
wp_signon()authentication - Uses Turnstile's
cfturnstile_check()function for validation - Validates the
cf-turnstile-responsePOST parameter - Returns appropriate error messages on failure
Styling:
- Turnstile widget centered in popup
- Custom CSS classes:
.show-login-turnstile,.show-login-cf-turnstile - Respects Turnstile theme settings (light, dark, auto)
- Proper spacing and layout integration
Show Login includes built-in integration with the Two Factor plugin for enhanced security.
- ✅ Automatic Detection - Activates only when Two Factor plugin is installed and user has 2FA enabled
- ✅ Seamless Redirect - Password verified users are redirected to standard login page for 2FA
- ✅ Clean UX - Clear messaging about 2FA requirement
- ✅ Zero Configuration - No setup needed beyond installing Two Factor plugin
When a user with 2FA enabled attempts to login via Show Login popup:
- User enters username and password in popup
- Credentials validated (including Turnstile if active)
- If password correct but 2FA required:
- User sees message: "Redirecting to two-factor authentication..."
- Automatically redirected to
wp-login.phpwith 2FA form already displayed - No need to re-enter username/password
- User completes 2FA verification (TOTP code, email code, etc.)
- After 2FA verification, user is logged in and returned to the original page
The Two Factor plugin uses complex interactive forms (TOTP codes, backup codes, U2F keys, etc.) that aren't suitable for a simple popup. By redirecting to the standard login page, we ensure:
- Full compatibility with all 2FA methods
- Proper security (no shortcuts around 2FA)
- Clean UX (users understand they're on a secure login page)
- No maintenance burden (Two Factor plugin handles all UI)
- Show Login intercepts the
wp_loginhook (priority 5) before Two Factor's handler (priority 10) - Prevents HTML output during AJAX requests
- Creates a 2FA login nonce using
Two_Factor_Core::create_login_nonce() - Returns JSON response with:
two_factor_required: trueflagredirect_urlwith pre-authenticated 2FA form URL
- URL includes:
action=validate_2fa,wp-auth-id,wp-auth-nonce,redirect_to - Clears temporary auth cookies (2FA not complete yet)
- JavaScript automatically redirects to the 2FA form
- After 2FA completion, user is redirected back to
redirect_toURL (original page)
Form Hooks:
// Add logo/branding after the title
add_action('show_login_after_title', function() {
echo '<div style="text-align: center; margin: 15px 0;">';
echo '<img src="' . esc_url(get_stylesheet_directory_uri() . '/images/logo.png') . '" alt="Logo" style="max-width: 200px;">';
echo '</div>';
});
// Add custom fields at the start of the form
add_action('show_login_form_start', function() {
echo '<input type="hidden" name="custom_field" value="data">';
});
// Add custom fields before the submit button
add_action('show_login_form_middle', function() {
echo '<div class="show-login-field">';
echo '<label for="custom-field">Custom Field</label>';
echo '<input type="text" id="custom-field" name="custom_field">';
echo '</div>';
});
// Add links at the end of the form
add_action('show_login_form_end', function() {
echo '<p><a href="/forgot-password">Forgot Password?</a></p>';
});Authentication Hooks:
// Before authentication attempt
add_action('show_login_before_authenticate', function($username) {
error_log("Login attempt for user: {$username}");
});
// After authentication attempt
add_action('show_login_after_authenticate', function($user, $credentials) {
if (is_wp_error($user)) {
error_log("Failed login: " . $user->get_error_message());
}
}, 10, 2);
// On successful login
add_action('show_login_success', function($user) {
error_log("Successful login for user ID: {$user->ID}");
// Send notification, log analytics, etc.
});Customize Labels:
add_filter('show_login_popup_title', function($title) {
return 'Welcome Back!';
});
add_filter('show_login_username_label', function($label) {
return 'Email Address';
});
add_filter('show_login_password_label', function($label) {
return 'Your Password';
});
add_filter('show_login_remember_label', function($label) {
return 'Keep me logged in';
});
add_filter('show_login_submit_label', function($label) {
return 'Sign In';
});Modify Authentication:
// Modify credentials before authentication
add_filter('show_login_credentials', function($credentials) {
// Add custom data
$credentials['custom_data'] = 'value';
return $credentials;
});
// Customize error messages
add_filter('show_login_error_message', function($message, $error) {
if ($error->get_error_code() === 'invalid_username') {
return 'The email address you entered is not registered.';
}
return $message;
}, 10, 2);Redirect Customization:
add_filter('show_login_redirect_url', function($redirect_url, $current_url) {
// Always redirect to a specific page after login
return home_url('/dashboard');
}, 10, 2);Button Styling:
// Change button background color
add_filter('show_login_button_bg_color', function($color) {
return '#e74c3c'; // Default: #0073aa
});
// Change button hover background color
add_filter('show_login_button_hover_bg_color', function($color) {
return '#c0392b'; // Default: #005a87
});
// Change button text color
add_filter('show_login_button_text_color', function($color) {
return '#ffffff'; // Default: #fff
});Loading State:
// Suppress loading spinner and status messages
// Popup stays hidden until AJAX confirms user is logged out
add_filter('show_login_suppress_loading_state', '__return_true');Rate Limiting:
// Disable rate limiting completely
add_filter('show_login_enable_rate_limiting', '__return_false');
// Change max attempts
add_filter('show_login_max_attempts', function($max) {
return 10; // Default: 5
});
// Change time window (in seconds)
add_filter('show_login_rate_limit_window', function($window) {
return 1800; // 30 minutes (default: 60 = 1 minute)
});
// Override IP detection
add_filter('show_login_client_ip', function($ip) {
// Use custom IP detection
return $_SERVER['CUSTOM_IP_HEADER'] ?? $ip;
});For custom authentication requirements beyond the built-in Two Factor plugin integration, you can use the authentication hooks:
// Example: Custom post-authentication validation
add_action('show_login_after_authenticate', function($user, $credentials) {
if (is_wp_error($user)) {
return; // Authentication failed, skip custom checks
}
// Example: Require custom condition to be met
if (!user_meets_custom_requirement($user->ID)) {
wp_logout(); // Log them back out
wp_send_json_error([
'message' => 'Additional authentication requirements not met.'
]);
}
}, 10, 2);The plugin provides filters for button colors (see Button Styling section above). For other style customizations, use CSS specificity:
/* Change popup width */
#show-login-popup {
max-width: 500px !important;
}
/* Change overlay opacity */
#show-login-overlay {
background-color: rgba(0, 0, 0, 0.9) !important;
}
/* Change input border color on focus */
.show-login-field input:focus {
border-color: #e74c3c !important;
box-shadow: 0 0 0 1px #e74c3c !important;
}The plugin exposes the showLoginData object to JavaScript:
// Available properties:
showLoginData.ajaxUrl // WordPress AJAX URL
showLoginData.nonce // Security nonce
showLoginData.redirectUrl // URL to redirect after loginshow-login/
├── show-login.php # Main plugin bootstrap
├── includes/
│ ├── class-show-login.php # Main controller
│ ├── class-show-login-assets.php # Asset management
│ ├── class-show-login-authenticator.php # Authentication handler
│ ├── class-show-login-popup.php # Popup rendering & AJAX check
│ ├── class-show-login-rate-limiter.php # Rate limiting logic
│ └── class-show-login-turnstile.php # Cloudflare Turnstile integration
├── assets/
│ ├── css/
│ │ └── show-login.css # Popup styles + Turnstile styles
│ └── js/
│ └── show-login.js # Front-end behavior + Turnstile loader
├── templates/
│ └── popup.php # Login popup HTML
├── README.md # Technical documentation
└── readme.txt # WordPress.org readme
The plugin follows a clean architecture pattern with separation of concerns:
Main Controller (Show_Login)
- Coordinates all plugin components
- Initializes dependencies
- Manages WordPress hooks
- Singleton pattern for single instance
Assets Handler (Show_Login_Assets)
- Enqueues CSS and JavaScript files
- Generates dynamic button color styles
- Manages script localization
- Handles redirect URL generation
Authenticator (Show_Login_Authenticator)
- Processes AJAX login requests
- Validates credentials
- Handles authentication success/failure
- Sanitizes error messages to prevent username enumeration
Rate Limiter (Show_Login_Rate_Limiter)
- IP-based rate limiting
- Transient storage for attempt tracking
- Configurable thresholds and windows
- CDN/proxy header support
Popup Handler (Show_Login_Popup)
- AJAX endpoint for login status check (cache-compatible)
- HTML rendering from template
- Returns popup data: HTML, nonce, redirect URL
- Instant feedback for logged-in users
Template (templates/popup.php)
- Semantic HTML markup
- WCAG accessibility compliant
- Action hooks for extensibility
- Filter hooks for customization
Turnstile Integration (Show_Login_Turnstile)
- Automatic detection of Turnstile plugin
- Dynamic script loading for cache compatibility
- Server-side validation before authentication
- Respects whitelist and all Turnstile settings
1. User visits page with ?sl=true (or ?sl=1, ?show_login=true, ?show_login=1)
↓
2. Page loads (may be cached HTML)
↓
3. Assets enqueued (CSS, JS on all frontend pages)
↓
4. JavaScript: Check if popup trigger parameter in URL
↓
5. If YES: Load Turnstile API dynamically (if Turnstile plugin active)
↓
6. Show loading spinner immediately
↓
7. AJAX: Check login status (show_login_check_popup)
↓
8. PHP: is_user_logged_in() → Return show=true/false + HTML/nonce/redirectUrl
↓
9. JavaScript: If logged in → Show "You're already logged in!" (1s) → Close
If logged out → Show "You're not logged in" (1s) → Show form
↓
10. Turnstile widget renders (if enabled) → User completes verification
↓
11. User submits form → AJAX request (show_login_authenticate)
↓
12. PHP: Rate limit check → Nonce verification → Turnstile validation → Sanitization
↓
13. Before hooks fire → wp_signon() → After hooks fire
↓
14. Success: Clear rate limit → Return success JSON
Failure: Log attempt → Return error JSON
↓
15. JavaScript: Success → window.location.href = redirectUrl
Failure → Show error in popup
✅ Good use cases:
- Members-only content requiring login
- Seamless login experience without leaving the page
- Custom landing pages with login CTAs
- Marketing campaigns with direct login links
❌ Not recommended for:
- Primary site-wide login (use standard WordPress login)
- High-security admin access
- As a complete replacement for wp-login.php
- Assets only load when needed (logged-out users with
?sl=true) - Inline CSS/JS eliminates HTTP requests
- Rate limiting uses efficient transients
- No database queries on every page load
- Always use HTTPS in production
- Install Simple Cloudflare Turnstile for CAPTCHA protection (see Cloudflare Turnstile Integration section)
- Install Two Factor plugin for 2FA support (see Two-Factor Authentication Integration section)
- Monitor failed login attempts using action hooks
- Keep WordPress and plugins updated
- Consider lowering rate limit thresholds for high-security sites
- Verify user is logged out
- Check URL has one of:
?sl=true,?sl=1,?show_login=true, or?show_login=1 - Check JavaScript console for errors
- Verify plugin is activated
- Verify Simple Cloudflare Turnstile plugin is installed and activated
- Check that Turnstile keys are configured in Settings > Turnstile
- Check JavaScript console for Turnstile API loading errors
- Verify user is not whitelisted in Turnstile settings
- Note: If using invisible mode (
appearance: execute), the widget is intentionally hidden - verification happens automatically on submit
- Verify both Site Key and Secret Key are correct in Turnstile settings
- Check that keys match the domain (Turnstile keys are domain-specific)
- Ensure server can make outbound HTTPS requests to
challenges.cloudflare.com - Check for JavaScript errors preventing Turnstile from loading
- Test with a different browser or incognito mode
- Check for JavaScript errors in console
- Verify
showLoginData.redirectUrlis correct - Check for conflicting plugins that modify redirects
- Clear transients:
delete_transient('show_login_attempts_*') - Verify IP detection is working correctly
- Check proxy/CDN configuration
This plugin is maintained by Caseproof.
GPL v2 or later. See LICENSE.
- Initial release
- Front-end login popup with URL parameter triggers (
?sl=true,?sl=1,?show_login=true,?show_login=1) - Built-in Cloudflare Turnstile integration with dynamic script loading
- Cache-compatible architecture (works with all caching plugins)
- Rate limiting and security features
- Extensive hooks for extensibility (2FA, CAPTCHA, custom fields)
- Full translation support
- WCAG accessibility compliant