Skip to content

Persist third-party plugin $_POST fields across the 2FA login screen (Fixes #705) - #925

Open
chetanupare wants to merge 2 commits into
WordPress:masterfrom
chetanupare:issue-705-persist-post-fields
Open

Persist third-party plugin $_POST fields across the 2FA login screen (Fixes #705)#925
chetanupare wants to merge 2 commits into
WordPress:masterfrom
chetanupare:issue-705-persist-post-fields

Conversation

@chetanupare

@chetanupare chetanupare commented Jul 6, 2026

Copy link
Copy Markdown

What?

This PR ensures that custom $_POST fields injected by third-party plugins (e.g., JavaScript detection flags, custom checkboxes) into the WordPress login form are preserved when the Two-Factor plugin interrupts the login flow to display its 2FA challenge.

Fixes #705

Why?

Currently, when a user successfully passes the initial username/password check, Two-Factor renders the login_html() interstitial form. In doing so, it drops any non-standard $_POST variables submitted from the original wp-login.php form. When the user eventually submits their 2FA code, plugins that rely on $_POST (for instance, during the attach_session_information hook) fail to find their data.

How?

A new recursive method Two_Factor_Core::print_custom_post_fields() has been added.

  1. It iterates over $_POST and outputs hidden elements for each field.
  2. It uses a rigorous blocklist to prevent echoing standard WordPress core fields (like log, wp-submit, _wpnonce, etc.), Two-Factor specific fields, and most importantly, passwords (pwd, password, user_pass). This ensures absolute security so that plaintext passwords are never inadvertently written to the HTML source code.
  3. Unit tests have been added to verify that custom fields (including nested arrays) are properly passed through, and that sensitive fields are rigorously blocked.

Use of AI Tools

AI assistance: No

Testing Instructions

  1. Install and activate a plugin that adds a custom field to the WP login form (e.g., nocache-bfcache from the issue, or write a simple mu-plugin that adds an using the login_form hook).
  2. Attempt to log in with an account that has Two-Factor enabled.
  3. On the 2FA interstitial screen, inspect the HTML source code.
  4. Verify that the custom field (my_custom_field) is present as a hidden input.
  5. Verify that sensitive fields (like pwd, password, user_pass, log, etc.) are absolutely not present in the hidden inputs.
  6. Verify that standard authentication still works normally.

Changelog Entry

Fixed - Preserve custom third-party $_POST variables during the 2FA login challenge.

Open WordPress Playground Preview

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: chetanupare <chetanupare@git.wordpress.org>
Co-authored-by: dd32 <dd32@git.wordpress.org>
Co-authored-by: westonruter <westonruter@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Two-Factor’s interrupted-login flow by preserving third-party $_POST fields across the 2FA interstitial form so downstream hooks (e.g., attach_session_information) can still read plugin-injected login inputs after 2FA completes.

Changes:

  • Adds a core helper to render non-blocklisted $_POST fields as hidden inputs on the 2FA interstitial form (including nested arrays).
  • Adds unit tests for the new $_POST passthrough behavior.
  • Introduces a new session-revalidation action/API (two_factor_revalidate_session / action_revalidate_session()), which is not described in the PR’s stated scope.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
class-two-factor-core.php Prints custom $_POST fields into the 2FA interstitial form; adds a new session revalidation action/API.
tests/class-two-factor-core.php Adds unit tests covering custom $_POST passthrough and the new revalidation redirect behavior.
Comments suppressed due to low confidence (1)

tests/class-two-factor-core.php:2767

  • This test also needs to avoid exit; if a redirect happens unexpectedly. Using do_redirect_callable() lets the test assert that no redirect occurred without risking the process terminating.
		// Should not throw an exception (no redirect)
		$exception = false;
		try {
			Two_Factor_Core::action_revalidate_session( 300 );
		} catch ( Two_Factor_Redirect_Exception $e ) {
			$exception = true;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread class-two-factor-core.php Outdated
}

$reauth_url = self::get_user_two_factor_revalidate_url();
$reauth_url = add_query_arg( 'redirect_to', urlencode( $redirect_to ), $reauth_url );
Comment thread class-two-factor-core.php Outdated
if ( in_array( $key, $blocklist, true ) ) {
continue;
}
self::print_hidden_inputs( $key, $value );
Comment thread class-two-factor-core.php Outdated
Comment on lines +2715 to +2723
private static function print_hidden_inputs( $name, $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $k => $v ) {
self::print_hidden_inputs( $name . '[' . $k . ']', $v );
}
} else {
echo '<input type="hidden" name="' . esc_attr( $name ) . '" value="' . esc_attr( $value ) . '" />' . "\n";
}
}
Comment thread tests/class-two-factor-core.php Outdated
Comment on lines +2731 to +2742
// Catch the redirect exception
$redirected = false;
try {
Two_Factor_Core::action_revalidate_session( 300, '/custom-redirect/' );
} catch ( Two_Factor_Redirect_Exception $e ) {
$redirected = true;
$location = $e->getMessage();
$this->assertStringContainsString( 'action=revalidate_2fa', $location );
$this->assertStringContainsString( 'redirect_to=%2Fcustom-redirect%2F', $location );
}

$this->assertTrue( $redirected, 'Expected wp_safe_redirect to throw Two_Factor_Redirect_Exception.' );
Comment thread class-two-factor-core.php Outdated
add_action( 'admin_init', array( __CLASS__, 'trigger_user_settings_action' ) );
add_filter( 'two_factor_providers', array( __CLASS__, 'enable_dummy_method_for_debug' ) );

add_action( 'two_factor_revalidate_session', array( __CLASS__, 'action_revalidate_session' ), 10, 2 );
Comment on lines +2805 to +2812
$_POST = array(
'pwd' => 'my_secret_password',
'password' => 'my_other_password',
'log' => 'admin',
'user_pass' => 'secret',
'rememberme' => '1',
'custom_ok' => 'allowed',
);
This safely copies unknown  variables into hidden fields on the 2FA interstitial screen, ensuring third-party login forms function properly without leaking standard WordPress fields or plaintext passwords.
@chetanupare
chetanupare force-pushed the issue-705-persist-post-fields branch from 4db3286 to e4d19b0 Compare July 27, 2026 06:30
@chetanupare

Copy link
Copy Markdown
Author

Hi @masteradhoc , I've cleaned up this PR branch ! The unrelated hook commit is gone, the $_POST fields are now properly unslashed, and the blocklist correctly handles nested arrays now.
Let me know if it looks good !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom login form input fields dropped during two-factor challenge

2 participants