Search by

cartpauj / show-login

cartpauj

WordPress front-end login popup triggered by a URL parameter (?sl=1).

Package info

github.com/cartpauj/show-login

Type:wordpress-plugin

pkg:composer/cartpauj/show-login

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.3.0 2026-10-25 14:39 UTC

This package is auto-updated.

Last update: 2026-10-25 14:39:24 UTC


README

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.

Features

  • 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, and with Wordfence 2FA right in the popup
  • Social Login - Shows the buttons from MemberPress and WishList Member Social Login, and from Login with Google, sending people back to the page they were on
  • Secure - Nonce verification, username enumeration prevention, and proper sanitization. Works with your security plugin for login limits
  • 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 - Every message, including the popup's JavaScript ones, can be translated. Works with Loco Translate out of the box
  • 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

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Installation

With Composer

composer require cartpauj/show-login

The package is a wordpress-plugin, so composer/installers puts it in wp-content/plugins/show-login/. Then activate it in the WordPress admin.

By hand

  1. Download show-login-x.y.z.zip from the latest GitHub release
  2. Upload it in Plugins → Add New → Upload Plugin (or unzip it into /wp-content/plugins/)
  3. Activate the plugin through the WordPress admin
  4. The plugin works automatically when the URL parameter is present

Usage

Basic Usage

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., sl or show_login)

User Experience

  1. User visits a page with a popup trigger parameter (e.g., ?sl=true)
  2. Popup appears instantly with loading spinner ("Checking login status...")
  3. Login status checked via AJAX (bypasses page cache)
  4. If logged out: Shows "You're not logged in" (1 second) → Login form appears
  5. If logged in: Shows "You're already logged in!" (1 second) → Popup closes
  6. User can close popup via:
    • X button in top-right corner
    • Clicking outside popup on overlay
    • Pressing ESC key
  7. User enters credentials and submits
  8. AJAX authentication happens without page reload
  9. Errors display in popup without disruption
  10. On success, page reloads and user is logged in

Security Features

Login Limits (Brute-Force Protection)

Show Login doesn't limit login attempts itself. That's a job for a security plugin like Wordfence, Limit Login Attempts Reloaded, or Solid Security. The popup logs people in through WordPress's own wp_signon(), so those plugins see and block popup logins exactly like logins on wp-login.php.

Removed: The built-in rate limiter is gone, along with the show_login_enable_rate_limiting, show_login_max_attempts, show_login_rate_limit_window, and show_login_client_ip filters. It trusted IP headers that visitors can fake, so it could be bypassed, and it could be used to lock real people out.

Nonce Verification

All AJAX requests are protected with WordPress nonces using check_ajax_referer().

Input Sanitization

  • 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

Username Enumeration Prevention

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

Error Message Filtering

Other errors (for example from a membership plugin) are passed through, but only simple HTML is kept: <strong>, <b>, <em>, <i>, <br>, and links. Anything else, like scripts or event handlers, is stripped. This runs before the show_login_error_message filter, so whatever your filter returns is shown as-is. Escape anything you add there.

Safe Redirects

Every redirect URL, including the one passed to the two-factor form, must stay on your site. Off-site URLs are replaced with a safe default.

Page Caching Compatibility

The plugin is fully compatible with all page caching solutions:

  • JavaScript checks ?sl=true parameter 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)
    • The spinner stays up for at least 1 second so it doesn't flash. The browser handles this wait, so the server never sits idle

Cloudflare Turnstile Integration

Show Login includes built-in integration with the Simple Cloudflare Turnstile plugin for CAPTCHA protection.

Features

  • ✅ 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

Setup

  1. Install and activate Simple Cloudflare Turnstile
  2. Configure your Cloudflare Turnstile Site Key and Secret Key in Settings > Turnstile
  3. 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.

Supported Settings

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

How It Works

When both plugins are active and Turnstile is configured:

  1. User visits page with popup trigger parameter
  2. Turnstile API loads dynamically (cache-compatible)
  3. Login popup appears with Turnstile widget between "Remember Me" and submit button
  4. User interaction:
    • Visible mode: User completes the visible challenge, then submits
    • Invisible mode: User fills form and clicks submit, verification happens automatically
  5. Turnstile token (cf-turnstile-response) sent with login request
  6. Server validates token before authentication via cfturnstile_check()
  7. If validation fails, user sees error message (customizable in Turnstile settings)
  8. 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

Filters

// 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;
});

Action Hooks

// 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}");
});

Technical Details

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-response POST 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

Two-Factor Authentication Integration

Show Login includes built-in integration with the Two Factor plugin for enhanced security.

Features

  • ✅ 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

How It Works

When a user with 2FA enabled attempts to login via Show Login popup:

  1. User enters username and password in popup
  2. Credentials validated (including Turnstile if active)
  3. If password correct but 2FA required:
    • User sees message: "Redirecting to two-factor authentication..."
    • Automatically redirected to wp-login.php with 2FA form already displayed
    • No need to re-enter username/password
  4. User completes 2FA verification (TOTP code, email code, etc.)
  5. After 2FA verification, user is logged in and returned to the original page

Why This Approach?

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)

Technical Notes

  • Show Login intercepts the wp_login hook (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: true flag
    • redirect_url with 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_to URL (original page)

Wordfence Two-Factor Integration

Users with Wordfence two-factor authentication can finish logging in without leaving the popup.

How It Works

  1. User enters username and password in the popup
  2. If the password is right and Wordfence wants a 2FA code, the popup shows a Two-Factor Code field with the message "Enter the code from your authenticator app."
  3. User enters the code from their authenticator app (or a Wordfence recovery code) and clicks Log In again
  4. Wordfence checks the code, and the user is logged in and stays on the same page

A wrong code shows Wordfence's own error and keeps the code field open. A wrong password shows the usual generic error, so the popup never reveals whether the password was right.

Technical Notes

  • Active only when Wordfence Login Security is installed (\WordfenceLS\Controller_WordfenceLS exists)
  • The code field is added with the show_login_form_middle action, hidden and disabled until it's needed, so it isn't sent with normal logins
  • When wp_signon() fails with Wordfence's wfls_twofactor_required error, the response includes code_required: true
  • The code is sent in Wordfence's own wfls-token field, so Wordfence validates it during wp_signon() exactly as it does on wp-login.php
  • Wordfence's reCAPTCHA option isn't supported in the popup

Social Login Integration

Show Login puts social login buttons at the top of the popup, above an "or" divider, when one of these is installed and set up:

Plugin Shown when
MemberPress Social Login, WishList Member Social Login (Caseproof) At least one provider is enabled and verified. One button per provider.
Login with Google (rtCamp) Its Client ID and Client Secret are set (in settings or with the WP_GOOGLE_LOGIN_CLIENT_ID / WP_GOOGLE_LOGIN_SECRET constants).

Nothing to configure. If none of them is set up, the popup looks exactly as before.

After logging in, people come back to the page they were on (minus ?sl=1), just like a password login.

Good to know

  • Social login leaves the page: the user goes to Google (or another provider) and comes back. It doesn't happen inside the popup.
  • If social login fails (for example, the Google account isn't linked to a user on the site), the plugin shows its error on wp-login.php, not in the popup.
  • The membership plugin's own login redirect rules still apply afterwards. For example, MemberPress may send a member with an expired subscription to their account page, and WishList Member's "login redirect override" setting takes priority.
  • Each plugin's button stylesheet is loaded on front-end pages for logged-out visitors, because the popup is added after the page loads.

Adding your own buttons

Anything printed on the show_login_social_buttons action shows up in the same spot, with the "or" divider added automatically. You get the URL to send people back to after login:

add_action('show_login_social_buttons', function ($redirect_url) {
    printf(
        '<a class="my-sso-button" href="%s">Log in with Company SSO</a>',
        esc_url(add_query_arg('return', rawurlencode($redirect_url), 'https://sso.example.com/start'))
    );
});

// Hide a built-in integration:
add_action('plugins_loaded', function () {
    remove_action('show_login_social_buttons', [Show_Login_Google::class, 'render_button'], 20);
    remove_action('show_login_social_buttons', [Show_Login_Social_Login::class, 'render_buttons'], 10);
}, 30);

Translations

Show Login works with Loco Translate with no setup:

  1. Install and activate Loco Translate.
  2. Go to Loco Translate → Plugins → Show Login and click New language.
  3. Leave the location on Loco's default (Custom, in wp-content/languages/loco/) and translate.

Save it there or in System (wp-content/languages/plugins/), not in the plugin's own folder. Plugin updates replace the plugin folder, so translations saved inside it would be lost.

When you save, Loco also builds the small .json files the popup's JavaScript messages need. Nothing else to do.

Good to know

  • The plugin ships only the blank template, languages/show-login.pot. It isn't on WordPress.org, so there are no automatic language packs.
  • Any tool that writes standard .po/.mo (and WordPress .json) files works too. Put them in wp-content/languages/plugins/, named like show-login-de_DE.mo.

Developer Documentation

Hooks & Filters

Action Hooks

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.
});

Filter Hooks

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';
});

// Wordfence 2FA code field (only shown to users who need it)
add_filter('show_login_2fa_code_label', function($label) {
    return 'Authenticator Code';
});

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');

Custom Authentication Flows

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);

Customizing Styles

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;
}

JavaScript API

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 login

Architecture

File Structure

show-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-turnstile.php      # Cloudflare Turnstile integration
│   ├── class-show-login-two-factor.php     # Two Factor integration
│   ├── class-show-login-social-login.php   # Caseproof Social Login integration
│   └── class-show-login-google.php         # Login with Google (rtCamp) 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
├── languages/
│   └── show-login.pot                      # Translation template (built by composer make-pot)
├── loco.xml                                # Tells Loco Translate where strings and template live
├── README.md                               # Technical documentation
└── readme.txt                              # WordPress.org readme

Architecture

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

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

Authentication Flow

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: Nonce verification → Sanitization → Turnstile validation
   ↓
13. Before hooks fire → wp_signon() → After hooks fire
   ↓
14. Success: Return success JSON
    Failure: Return error JSON (security plugins see it via wp_login_failed)
   ↓
15. JavaScript: Success → window.location.href = redirectUrl
               Failure → Show error in popup

Best Practices

When to Use This Plugin

✅ 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

Performance Considerations

  • Assets only load when needed (logged-out users with ?sl=true)
  • Inline CSS/JS eliminates HTTP requests
  • No database queries on every page load

Security Recommendations

  1. Always use HTTPS in production
  2. Install Simple Cloudflare Turnstile for CAPTCHA protection (see Cloudflare Turnstile Integration section)
  3. Install Two Factor plugin for 2FA support (see Two-Factor Authentication Integration section)
  4. Install a security plugin (Wordfence, Limit Login Attempts Reloaded, Solid Security, etc.) to limit login attempts. It covers the popup automatically
  5. Monitor failed login attempts using action hooks
  6. Keep WordPress and plugins updated

Troubleshooting

Popup doesn't appear

  • 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

Turnstile widget not showing

  • 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

Turnstile validation always failing

  • 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

Redirect not working after login

  • Check for JavaScript errors in console
  • Verify showLoginData.redirectUrl is correct
  • Check for conflicting plugins that modify redirects

Developers

Everything you need to work on Show Login itself. (For hooks and filters, see Developer Documentation above.)

Releasing

Follow RELEASES.md every time. It's the step-by-step checklist for bumping the version, tagging, and checking that Packagist and GitHub picked it up.

Setup

You need PHP 7.4+, Composer, Node 20+, and a local MySQL/MariaDB server.

composer install
npm install

# Download WordPress + its test library into ./tmp and create a test database.
# WARNING: the database named in DB_NAME is dropped and recreated. Default: show_login_tests.
DB_USER=root DB_PASS=yourpassword composer tests-install

# Or test against a specific WordPress version:
DB_USER=root DB_PASS=yourpassword composer tests-install -- 6.9.4

Other settings: DB_HOST (default localhost, host:port works) and DB_NAME.

Running checks

Command What it does
composer test PHP tests (PHPUnit + the WordPress test library)
composer test:coverage PHP tests, then fails if line coverage is under 90%. Needs pcov or xdebug.
npm test JavaScript tests (Jest + jsdom)
npm run test:coverage JavaScript tests, fails under 90% coverage
composer cs-check Coding standards (PSR-12 + WordPress security sniffs + PHP 7.4 compatibility)
composer cs-fix Auto-fixes what it can
composer make-pot Rebuilds the translation template languages/show-login.pot. Run it after adding or changing any user-facing text, then commit the file.
bin/check-package.sh Checks that the package/zip contains only plugin files

Test layout

tests/
├── bootstrap.php          # Loads WordPress test library + the plugin
├── Framework/             # Base test cases (AjaxTestCase runs admin-ajax endpoints)
├── Fixtures/              # Stand-ins for the Two Factor and Turnstile plugins
├── TestCases/             # PHP tests: *Test.php
└── js/                    # Jest tests for assets/js/show-login.js

The PHP tests never make network calls (WP_HTTP_BLOCK_EXTERNAL). Two Factor and Simple Cloudflare Turnstile are replaced by small fakes in tests/Fixtures/, so neither plugin needs to be installed.

Continuous integration

.github/workflows/tests.yml runs on every pull request (and by hand from the Actions tab):

  • PHP tests: PHP 7.4, 8.2 and 8.4 against WordPress nightly, the newest release, and the newest release of the previous major (e.g. 7.1.x and 6.9.x). The WordPress versions are looked up automatically. Nightly failures are reported but don't fail the build.
  • Coverage: the PHP 8.4 + newest-WordPress run and the JS run both fail under 90%.
  • Coding standards, composer.json validation, and the package contents check.
  • Translation template: fails if user-facing text changed but languages/show-login.pot wasn't rebuilt. Run composer make-pot and commit. A version bump trips it too, since the template records the version.

What ships

Composer/Packagist and the GitHub release zip are both built with git archive, which skips everything marked export-ignore in .gitattributes (tests, tooling, CI config, this kind of documentation). If you add a dev-only file or folder, add it there too. bin/check-package.sh fails CI if you forget.

Contributing

This plugin is maintained by cartpauj. Issues and pull requests are welcome on GitHub.

License

GPL v2 or later. See LICENSE.

Changelog

1.3.0

  • New: People who use Wordfence two-factor authentication can now log in from the popup. After their password, the popup asks for the code from their authenticator app. Before, they got a "CODE REQUIRED" error with nowhere to enter it.
  • New: The show_login_2fa_code_label filter changes the label on the code field.

1.2.0

  • Breaking: The built-in login attempt limit is gone. It could be tricked with fake IP addresses and used to lock real people out. Use a security plugin like Wordfence, Limit Login Attempts Reloaded, or Solid Security instead. It protects the popup automatically. The show_login_enable_rate_limiting, show_login_max_attempts, show_login_rate_limit_window, and show_login_client_ip filters no longer do anything.
  • Fix: The show_login_redirect_url filter now works. Before, it never ran in the popup.
  • Fix: Opening the popup no longer makes your server wait a second for every visitor. The spinner still shows for at least a second, but the browser handles it.
  • Fix: If the server sends back a broken reply, the spinner closes instead of getting stuck.
  • Security: After two-factor login, people can only be sent to pages on your site.
  • Security: Error messages from other plugins are cleaned up before the popup shows them, so they can't run scripts.

1.1.1

  • Fix: The popup's status and error messages ("Checking login status...", "You're not logged in", and others) were always in English. They can now be translated.
  • New: Works with Loco Translate out of the box. See Translations.
  • Dev: Translation template (languages/show-login.pot), composer make-pot, and a CI check that the template is up to date.

1.1.0

  • New: Social login buttons in the popup for MemberPress and WishList Member Social Login, and Login with Google. After logging in, people return to the page they were on. See Social Login Integration.
  • New: show_login_social_buttons action for adding your own login buttons above the form.
  • New: Install with Composer (composer require cartpauj/show-login) or download the zip from GitHub releases.
  • Fix: Links with special characters (like %20) were changed when sending people back after login or two-factor.
  • Fix: The lockout message said "try again in 0 minutes" on sites using Redis or Memcached.
  • Fix: Cloudflare's Turnstile script no longer loads on sites that don't use Turnstile.
  • Fix: Translations couldn't be found because the languages folder path was wrong.
  • Fix: Plugin and readme links pointed at the wrong GitHub repository.
  • Dev: PHP and JavaScript test suites, CI, coding standards, and a release process (see RELEASES.md).

1.0.0

  • 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