Using TwoFactorX for 2FA on the front end

Hi folks,

I’ve installed TwoFactorX [forked from GoogleAuthenticatorX by @jako] on a few sites and it largely works well to help secure the Manager.

There are now docs describing how to deploy this to front end users using the Login extra.

As per the docs, I’ve altered my Login call:

[[!login? &preHooks=`TwoFactorXLogin`]]

and added the following field to the login form tpl:

<input type="text" name="code" />

However, although the code field appears - in all cases I can log in without specifying the auth code - just supplying the user & password. Even supplying an incorrect code gets me logged in.

TwoFactorX is enabled and works as expected on the Manager login page. I’ve tested this on two different sites.

I’d love to get this working - any help much appreciated.

MODX 3.0.5-pl
TwoFactorX 1.0.5-pl
Login 1.9.13-pl

I couldn’t reproduce any general problem with the “TwoFactorXLogin” pre-hook.


This implies that either the snippet never gets executed or that the user settings are wrong.

Try adding some log messages to the snippet code to ‘debug’ the issue.

For example, add
$this->modx->log(\modX::LOG_LEVEL_ERROR, 'TwoFactorXLogin | $settings = ' . print_r($settings, 1));
before this line in the code, to log the user settings.

Then check the MODX error log.

Hi @halftrainedharry and thanks …

These errors are generated when the login form is loaded:

[2024-05-30 08:16:58] (ERROR @ /home/xxx/yyy/core/components/twofactorx/src/Snippets/LoginHook.php : 38) PHP warning: Undefined array key "username"
[2024-05-30 08:16:58] (ERROR @ /home/xxx/yyy/core/components/twofactorx/src/Snippets/LoginHook.php : 39) PHP warning: Undefined array key "code"
[2024-05-30 08:16:58] (ERROR @ /home/xxx/yyy/core/components/twofactorx/src/Snippets/LoginHook.php : 41) PHP warning: Undefined array key "service"

Apologies - I didn’t spot the user output:

[2024-05-30 08:24:18] (ERROR @ /home/xxx/yyy/core/components/twofactorx/src/Snippets/LoginHook.php : 50) TwoFactorXLogin | $settings = Array
(
    [inonetime] => 
    [secret] => 3WIF0PWGQ5ECTM6A
    [uri] => otpauth://totp/username?secret=3WIF0PWGQ5ECTM6A&issuer=MODX+Test+Site
    [iv] => aAgJROH9/2iLF5R3SqQBmA==
    [totp_disabled] => 
)

I’ve changed some of these values for security.

The snippet only checks the code for this user if the value of “inonetime” is yes
$settings['inonetime'] == 'yes'.

Hmm …

What does that variable actually relate to in the Manager?

Current settings for TwoFactorX:

I believe it’s the system setting twofactorx.enable_onetime.

There is also a user setting (“Manage” → “Users” → edit the user (you use for the login) → tab “Extended Fields” → “twofactorx” → “inonetime”). But this value is encrypted.

I don’t completely understand how these two settings interact. I’m not familiar with the code of this extra.

OK thanks Harry.

Did the front-end login 2FA work out of the box for you?

In the user management there is also a tab “TwoFactorX”.
Maybe try one of the buttons there (for the user in question), for example “Reset Secret”, to see if that fixes the value for inonetime.

In each user’s Extended Fields tab, there is no mention of inonetime under twofactorx.

I tried resetting the Secret and disabling / enabling TwoFactorX for a user but there’s still no sign of the inonetime setting.

Do the other settings (secret, uri, iv) exist? Is it just the inonetime setting that’s missing?


If you delete the whole “twofactorx” container in the “Extended Fields”, and then reset the secret, are the settings recreated without the inonetime setting?

Yes, they do, but interestingly there’s also one called incourtesy - which I think is the equivalent setting from GoogleAuthenticatorX - I did previously have that installed but it was uninstalled a while ago.

Anyway - let’s assume there’s some conflict going on there - and I’ll switch over to my other test site - which also has TwoFactorX but has never had GAX installed.

It does, however, suffer from the same issue.

Looking at the User’s Extended Fields, I can see that inonetime is present as it should be:

image

With your error logging addition to the snippet - when we log in:

[2024-05-30 09:53:14] (ERROR @ /home/xxx/yyy/core/components/twofactorx/src/Snippets/LoginHook.php : 50) TwoFactorXLogin | $settings = Array
(
    [inonetime] => no
    [secret] => BBP3FD44MIEJAEEW
    [uri] => otpauth://totp/username?secret=BBP3FD44MIEJAEEW&issuer=MODX+3+Test
    [iv] => RbDR4pLiMw1o4DWhQBiuRg==
    [totp_disabled] => 
)

The thing is - as I understand it - inonetime allows the user to log in once without requiring an auth code. Presumably inonetime would be set to no once the user has logged in once.

So if it was set to yes I’d expect the user to be allowed to log in without 2FA.

In this case, it’s set to no and still we can log in without 2FA

Yes, I guess there’s a logic error in the snippet code.

Switching the line:

if (!$settings['totp_disabled'] && $settings['inonetime'] == 'yes') {

to:

if (!$settings['totp_disabled'] && $settings['inonetime'] == 'no') {

… appears to result in the expected behaviour. Hard to tell if it’s the right way to fix it or not.

I’ll update the issue on Github.

Thanks for your help, Harry :+1:

As the inonetime setting only seems to be relevant for manager access and not for front-end access, the value of this setting probably shouldn’t be checked at all:

if (!$settings['totp_disabled']) {
1 Like

This topic was automatically closed 2 days after discussion ended and a solution was marked. New replies are no longer allowed. You can open a new topic by clicking the link icon below the original post or solution and selecting “+ New Topic”.