CMS:USER_2FA

Shows the second factor of the signed-in frontend user, sets it up (authenticator app or code by email) and switches it off.

(CMS:USER_2FA [parameter=value …])

Method: Modules\Record\RecordUser::cmsTwoFactor()

Parameters

Parameter Value Default Required Description
function status / totp_setup / totp_enable / code_enable / disable / recovery_renew status What the command does (see below).
submit text value – Writing functions run only if this field contains a text.
data PHP variable name data Name of the PHP array with the inputs.
codefield field name code Input field for the app code in data[…].
currentfield field name current_password Input field for the password in data[…].
message[…] default message – Error messages. In the index: use case; in the value: message text.
cookiename cookie name usess Name of the session cookie [a-z0-9].
csrf true / false true true: Writing functions require a token from CMS:USER_TOKEN.

Usage

The command belongs on a protected page of the customer account (after CMS:USER_PROTECT). It takes effect only if the profile area allows a second factor (setting “Second factor” is not “off”; otherwise the message policy).

function Effect Confirmed with
status read only –
totp_setup creates a new key for the authenticator app – nothing is saved yet. $set['totp_setup'] contains token, qr (ready-made HTML with the QR code), secret (to type in, in groups of four), uri (otpauth://…, which opens the app directly on the phone) and qr_script (address of the QR library) –
totp_enable saves the key and creates ten recovery codes ($set['recovery_codes'], shown in plain text only at this moment) data[token] and the first code from the app (data[code])
code_enable code by email at every sign-in password (data[current_password]); if an app is set up, the app code
disable switches off. If the area always requires a second factor, the code is then sent by email instead of from the app app code or recovery code, or password
recovery_renew new recovery codes; the old ones expire app code

After each function, $set contains: second_factor ('', totp, email), second_factor_policy (setting of the area: none, optional, new_device, required), second_factor_since (UTC) and recovery_count. After a change, second_factor_changed is set (totp_enabled, totp_disabled, code_enabled, code_disabled, recovery_renewed) – for example for a notice email to the frontend user.

To set up the app, the frontend user scans the QR code with the app. It contains the key, not the codes – the app generates those itself afterwards, six digits each. Alternatively, the user types in the key or opens the link on the phone. $set['totp_setup']['qr'] brings everything: an element with the QR code, the qrcode-generator library (the same as in the back end, taken from its assets) and a small script. Both use the nonce of the page, so the setup also runs under an enforced script policy. Output it only with echo, and control the size with CSS (.c9-qr { width: 200px }).

<?php
$isPost = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
$action = $isPost ? (string)($_POST['action'] ?? '') : (isset($_GET['setup']) ? 'totp_setup' : 'status');
$submit = $isPost ? '1' : '';
?>
(CMS:USER_PROTECT errortemplate=u_login)
(CMS:USER_2FA function="$action" submit="$submit")
<p class="error">(CMS:MESSAGE)</p>
<?php if (!empty($set['recovery_codes'])) { ?>
  <p>Your recovery codes (please keep them safe):</p>
  <ul><?php foreach ($set['recovery_codes'] as $c) echo '<li>' . htmlspecialchars($c) . '</li>'; ?></ul>
<?php } ?>
<?php if (!empty($set['totp_setup'])) { $s = $set['totp_setup']; ?>
  <?php echo $s['qr']; ?>
  <p>Key for your authenticator app: <code><?php echo htmlspecialchars($s['secret']); ?></code>
     – or <a href="<?php echo htmlspecialchars($s['uri']); ?>">open it in the app</a>.</p>
  <form method="post">
    (CMS:USER_TOKEN)
    <input type="hidden" name="action" value="totp_enable">
    <input type="hidden" name="data[token]" value="<?php echo htmlspecialchars($s['token']); ?>">
    <input name="data[code]" autocomplete="one-time-code" required>
    <button>Switch on</button>
  </form>
<?php } elseif ($set['second_factor'] === '') { ?>
  <p><a href="?setup=1">Set up authenticator app</a></p>
<?php } ?>

Message indexes: auth (not signed in), policy, code (app code wrong), current (password wrong), setup (setup expired – 15 minutes), blocked (too many failed attempts), token. Wrong codes and passwords count in the same way as at sign-in.

If a frontend user has lost their phone and their recovery codes, an administrator resets the second factor in the back end (profile record, menu “Second factor”).

Parameters in detail

function

(CMS:USER_2FA function=status)
(CMS:USER_2FA function="$action" submit="$submit")

submit

Only writing functions need it – they belong in a POST form:

<?php $submit = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' ? '1' : ''; ?>

data, codefield, currentfield

(CMS:USER_2FA function=disable submit="$submit" data=sec codefield=otp currentfield=pw)

message

(CMS:USER_2FA function="$action" submit="$submit" message[code]="The code is not correct.")

← Overview

As of camindo CMS 9.1.3