Skip to content

Fix Sign in with Google for accounts using the WordPress Two-Factor plugin#13118

Open
shervElmi wants to merge 9 commits into
developfrom
bug/11032-siwg-two-factor-account-handling
Open

Fix Sign in with Google for accounts using the WordPress Two-Factor plugin#13118
shervElmi wants to merge 9 commits into
developfrom
bug/11032-siwg-two-factor-account-handling

Conversation

@shervElmi

@shervElmi shervElmi commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Addresses issue:

Relevant technical choices

  • Added a TWO_FACTOR_ENABLED_PROVIDERS_META_KEY constant for the _two_factor_enabled_providers meta key instead of repeating the literal.
  • Split the test double into its own FakeTwoFactorAuthenticator file instead of an inline subclass.
  • Placed the check in the base Authenticator, so WooCommerce_Authenticator inherits it.
  • Routed the two-factor error through the end error check in authenticate_user() instead of a duplicate one.
  • Wrote user_has_two_factor() and the test double's override as single returns.

PR Author Checklist

  • My code is tested and passes existing unit tests.
  • My code has an appropriate set of unit tests which all pass.
  • My code is backward-compatible with WordPress 5.2 and PHP 7.4.
  • My code follows the WordPress coding standards.
  • My code has proper inline documentation.
  • I have added a QA Brief on the issue linked above.
  • I have signed the Contributor License Agreement (see https://cla.developers.google.com/).

Do not alter or remove anything below. The following sections will be managed by moderators only.

Code Reviewer Checklist

  • Run the code.
  • Ensure the acceptance criteria are satisfied.
  • Reassess the implementation with the IB.
  • Ensure no unrelated changes are included.
  • Ensure CI checks pass.
  • Check Storybook where applicable.
  • Ensure there is a QA Brief.
  • Ensure there are no unexpected significant changes to file sizes.

Merge Reviewer Checklist

  • Ensure the PR has the correct target branch.
  • Double-check that the PR is okay to be merged.
  • Ensure the corresponding issue has a ZenHub release assigned.
  • Add a changelog message to the issue.

When the Two-Factor plugin is active, turn off two-factor
authentication for accounts that Sign in with Google creates, and
return an error instead of linking an existing account that already
uses two-factor. An account created with two-factor left on breaks the
later sign-in, and linking one would remove its challenge while its
password still works.
Cover the new authenticator behavior with a test double that fakes the
Two-Factor plugin state, since the test environment has no Two-Factor
plugin.
Confirm the login screen adds the two-factor message for the new error
code, and leaves an unrecognized code untouched.
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

🤖 This comment is automatically updated by CI workflows. Each section is managed independently.

🎭 Playwright reports for e6f2957:

📦 Build files for e6f2957:

Modify the assertion in the login error handling test to check for the
specific error message related to existing accounts using two-factor
authentication. This ensures that the test accurately reflects the
expected behavior when two-factor authentication is enabled.
@shervElmi
shervElmi marked this pull request as ready for review July 16, 2026 17:34
Update the `authenticate_user` method to streamline user
creation logic and enhance readability. Adjust the `user_has_two_factor`
method to simplify the conditional checks for two-factor plugin
activation.
Comment thread includes/Modules/Sign_In_With_Google/Authenticator.php Outdated
Comment thread includes/Modules/Sign_In_With_Google/Authenticator.php Outdated
Comment thread includes/Modules/Sign_In_With_Google/Authenticator.php Outdated
This change allows users to sign in with Google without being blocked
by the Two-Factor plugin's login challenge. It modifies the
Authenticator class to remove the two-factor login check during
authentication. Additionally, tests are updated to verify this
behavior for both new and returning users.
Comment on lines +212 to +217
if ( $this->is_two_factor_plugin_active() ) {
$two_factor_challenge_priority = has_action( 'wp_login', array( 'Two_Factor_Core', 'wp_login' ) );
if ( false !== $two_factor_challenge_priority ) {
remove_action( 'wp_login', array( 'Two_Factor_Core', 'wp_login' ), $two_factor_challenge_priority );
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is not very reliable because it relies on the method name in the Two_Factor_Core class, which may be renamed at any point in the future. I think what we can do is override the enabled providers for the current user.

add_filter( 'two_factor_enabled_providers_for_user', function( $enabled_providers, $user_id ) use ( $user ) {
    return $user_id !== $user->ID ? $enabled_providers : array();
}, 10, 2 );

We can use this filter unconditionally because if the 2FA plugin is not installed, this filter will never be executed and will just be ignored. So, is_two_factor_plugin_active and related functions are not needed.

Comment on lines +255 to +259
// Don't link an account that has two-factor authentication enabled,
// as signing in with Google would bypass the account's two-factor challenge.
if ( $this->user_has_two_factor( $user->ID ) ) {
return new WP_Error( self::ERROR_TWO_FACTOR_ENABLED );
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you remind me why we shouldn't link an account? I don't see why it can be bad.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If we link the account automatically and it has two-factor turned on, the person can sign in with Google and skip the second code. Two-factor is there so the password alone isn't enough. Linking would let someone in without that second step. The user can still link Google from their own profile page. So we only block the automatic link, not the one they do themselves.

AC says linking would "remove 2FA". That was true, which changed the account and turned two-factor off. But now doesn't change anything. It only skips the check for that one Google sign-in.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we link the account automatically and it has two-factor turned on, the person can sign in with Google and skip the second code.

Yes, if the user signs in using Google OAuth, then they won't need to enter their 2FA code. That is what happens when we call skip_two_factor_for_user in the sign_in_user method. So, I don't see why we shouldn't link Google and WordPress accounts automatically if 2FA is enabled.

AC says linking would "remove 2FA".

I doubt this is a valid statement. When we link accounts, we just save Google user ID in the user metadata. That's all that happens here, and it can't remove 2FA for that user.

I think we need to remove this and continue automatically link user accounts.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh... I think I got what you mean. So, we want to prevent users from signing in using their Google accounts if their accounts already have two-factor authentication enabled. But if they manually connect a Google account in the admin, they should be able to sign in with Google. Right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, exactly! 👍

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Here's a small difference. The block is on linking, not on signing in.

This change modifies the authentication process to bypass the Two-Factor
plugin's login challenge when users sign in with Google. It ensures that
the two-factor providers are cleared for the signed-in user while leaving
other users' settings intact.
Comment on lines +324 to +326
protected function is_two_factor_plugin_active() {
return class_exists( 'Two_Factor_Core' );
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't think we need this one-line method that is used only once in another method in this class. Let's call class_exists directly in the user_has_two_factor method.

Comment on lines +354 to +363
protected function skip_two_factor_for_user( $user ) {
add_filter(
'two_factor_enabled_providers_for_user',
function ( $enabled_providers, $user_id ) use ( $user ) {
return $user_id === $user->ID ? array() : $enabled_providers;
},
10,
2
);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same here, one-line method used only once doesn't make too much sense. Let's add the filter directly in the sign_in_user method.

Comment on lines +357 to +359
function ( $enabled_providers, $user_id ) use ( $user ) {
return $user_id === $user->ID ? array() : $enabled_providers;
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's use an arrow function to make this a bit more concise.

Comment on lines +336 to +338
protected function user_has_two_factor( int $user_id ) {
return $this->is_two_factor_plugin_active() && \Two_Factor_Core::is_user_using_two_factor( $user_id ); // @phpstan-ignore class.notFound (Two_Factor_Core comes from the optional Two-Factor plugin, and is_two_factor_plugin_active() confirms it exists.)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So, if we want to prevent users from signing in with their Google accounts if two-factor security is enabled and their accounts are not linked, then we need to check whether their accounts are linked to correctly validate whether the user can sign in or not.

$error->add( self::MODULE_SLUG, __( 'The user is not registered on this site.', 'google-site-kit' ) );
break;
case Authenticator::ERROR_TWO_FACTOR_ENABLED:
$error->add( self::MODULE_SLUG, __( 'An existing account using two-factor authentication was detected with that email address. To connect an account using Sign in with Google, two-factor auth must be disabled for your user account.', 'google-site-kit' ) );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We need to rephrase this message to correctly inform users what they need to do.

Comment on lines +249 to +250
if ( $user ) {
// Don't link an account that has two-factor authentication enabled,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's flip this if statement to return early if the user is not found:

$user = get_user_by( 'email', $payload['email'] );
if ( ! $user ) {
    return null;
}

if ( $this->user_has_two_factor( $user->ID ) ) {
    return new WP_Error( self::ERROR_TWO_FACTOR_ENABLED );
}

$user_options = clone $this->user_options;
$user_options->switch_user( $user->ID );
$user_options->set( Hashed_User_ID::OPTION, $google_user_hashed_id );

return $user;

Comment on lines 1054 to 1059
do_action(
'googlesitekit_render_sign_in_with_google_button',
array(
'class' => 'googlesitekit-sign-in-with-google__existing-user-connect-button',
)
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Here we render the sign-in button that allows users to connect existing accounts with Google accounts. This won't work in the current implementation because we will return an error saying that existing accounts have 2FA enabled.

What we need to do is to set a transient for this user that will indicate that the user wants to connect their accounts and check that transient in the find_user method to bypass user_has_two_factor check if the user has this transient.

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.

2 participants