L’autenticazione a due fattori (2FA) aggiunge un secondo controllo oltre a utente e password, riducendo drasticamente i rischi di furto di account. In questa guida trovi due strade: plugin pronti per andare online in pochi minuti e un esempio minimale per inviare OTP via email senza dipendenze esterne.
Che cos’è il 2FA (e quali metodi usare)
- TOTP (app di autenticazione): codici a tempo in Google Authenticator, 1Password, Authy, Microsoft Authenticator. Sicuro e offline.
- WebAuthn/FIDO2: chiavi di sicurezza (YubiKey, passkey). È lo stato dell’arte.
- OTP via email: comodo e senza app, buona soluzione “minimale”.
- SMS: pratico ma meno sicuro; evita se possibile.
Opzione 1 — Attivare il 2FA con un plugin
- Vai in Plugin → Aggiungi nuovo e cerca “two factor” o “2FA”. Scegli un plugin affidabile con aggiornamenti recenti e molte installazioni attive.
- Installa e attiva. Poi apri le Impostazioni del plugin per:
- Abilitare TOTP, Email OTP e (se supportato) WebAuthn/chiavi di sicurezza.
- Obbligare il 2FA per determinati ruoli (es. Amministratori, Editor).
- Generare codici di backup da conservare in un password manager.
- Configurare il grace period (giorni per attivare il 2FA prima di renderlo obbligatorio).
- Comunica agli utenti come attivarlo nel proprio profilo (Il mio profilo → Sicurezza), fornendo istruzioni e link alla knowledge base.
Opzione 2 — Esempio minimale: OTP via email senza dipendenze
Di seguito trovi un’implementazione didattica basata su OTP via email. Funziona così:
- L’utente inserisce utente/password nella pagina di login.
- Se le credenziali sono corrette, WordPress non conclude il login: genera un codice OTP, lo invia via email e chiede di confermarlo.
- L’utente inserisce l’OTP in una pagina dedicata (
/2fa); se è valido e non scaduto, si completa l’accesso.
Pro: zero dipendenze, immediato. Contro: non è forte quanto TOTP/WebAuthn. Usalo come base o fallback.
1) Aggiungi il codice al tema child o a un plugin mu-plugin
Incolla questo blocco in wp-content/mu-plugins/wp-2fa-email.php (consigliato) oppure nel functions.php del tema child:
<?php
/**
* 2FA minimale via Email (OTP a 6 cifre).
* Salva come: wp-content/mu-plugins/wp-2fa-email.php
*/
if (!defined('ABSPATH')) { exit; }
class WP_Minimal_Email_2FA {
const TRANSIENT_PREFIX = 'wp2fa_pending_';
const TRANSIENT_TTL = 10 * MINUTE_IN_SECONDS; // OTP valido 10 minuti
const OTP_LENGTH = 6;
public function __construct() {
add_filter('authenticate', [$this, 'intercept_login'], 30, 3);
add_shortcode('wp_2fa', [$this, 'render_2fa_form']);
add_action('init', [$this, 'maybe_handle_2fa_submit']);
}
/**
* Dopo username/password corrette, invia OTP e blocca il login
*/
public function intercept_login($user, $username, $password) {
if ($user instanceof WP_Error) {
return $user; // credenziali errate o altri errori
}
if (!$user instanceof WP_User) {
return $user; // niente da fare
}
// Genera token di sessione "pending" e OTP
$token = wp_generate_password(20, false, false);
$otp = $this->generate_otp();
$payload = [
'user_id' => $user->ID,
'otp_hash' => wp_hash_password($otp),
'created' => time(),
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'user_agent'=> $_SERVER['HTTP_USER_AGENT'] ?? '',
];
set_transient(self::TRANSIENT_PREFIX . $token, $payload, self::TRANSIENT_TTL);
// Invia OTP via email
$to = $user->user_email;
$subject = sprintf(__('Il tuo codice di verifica (%s)'), get_bloginfo('name'));
$message = sprintf(
__("Ciao %s,\n\necco il tuo codice di verifica per accedere a %s:\n\n%s\n\nScade tra 10 minuti.\nSe non hai richiesto il codice, ignora questa email."),
$user->display_name ?: $user->user_login,
home_url(),
$otp
);
wp_mail($to, $subject, $message);
// Messaggio con link alla pagina 2FA
$twofa_url = add_query_arg(['token' => $token], home_url('/2fa/'));
$error_msg = sprintf(
wp_kses_post(__('Abbiamo inviato un codice di verifica al tuo indirizzo email. <a href="%s">Inseriscilo qui per completare l\'accesso</a>.', 'default')),
esc_url($twofa_url)
);
return new WP_Error('two_factor_required', $error_msg);
}
/**
* Shortcode [wp_2fa] per mostrare il form OTP
*/
public function render_2fa_form($atts) {
$token = isset($_GET['token']) ? sanitize_text_field($_GET['token']) : '';
$nonce = wp_create_nonce('wp2fa_verify');
ob_start();
?>
<h2>Verifica in due passaggi</h2>
<p>Inserisci il codice di 6 cifre ricevuto via email.</p>
<form method="post">
<label for="otp">Codice OTP</label><br>
<input type="text" id="otp" name="otp" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required><br><br>
<input type="hidden" name="token" value="<?= esc_attr($token); ?>">
<input type="hidden" name="wp2fa_action" value="verify">
<input type="hidden" name="wp2fa_nonce" value="<?= esc_attr($nonce); ?>">
<button type="submit">Verifica e accedi</button>
</form>
<?php
return ob_get_clean();
}
/**
* Gestisce l'invio del form OTP e completa il login
*/
public function maybe_handle_2fa_submit() {
if (!isset($_POST['wp2fa_action']) || $_POST['wp2fa_action'] !== 'verify') {
return;
}
if (!isset($_POST['wp2fa_nonce']) || !wp_verify_nonce($_POST['wp2fa_nonce'], 'wp2fa_verify')) {
wp_die(__('Token di sicurezza non valido.'));
}
$token = isset($_POST['token']) ? sanitize_text_field($_POST['token']) : '';
$otp = isset($_POST['otp']) ? preg_replace('/\D+/', '', $_POST['otp']) : '';
if (strlen($otp) !== self::OTP_LENGTH || empty($token)) {
wp_die(__('Codice o token non valido.'));
}
$payload = get_transient(self::TRANSIENT_PREFIX . $token);
if (!$payload) {
wp_die(__('Codice scaduto o non trovato. Riprova ad accedere.'));
}
if (!wp_check_password($otp, $payload['otp_hash'])) {
wp_die(__('Codice errato. Riprova.'));
}
$user_id = intval($payload['user_id']);
$user = get_user_by('id', $user_id);
if (!$user) {
wp_die(__('Utente non trovato.'));
}
// Completa l'accesso
wp_set_current_user($user_id);
wp_set_auth_cookie($user_id, true); // true = "ricordami"
delete_transient(self::TRANSIENT_PREFIX . $token);
// Reindirizza alla bacheca
wp_safe_redirect(admin_url());
exit;
}
private function generate_otp() {
$min = pow(10, self::OTP_LENGTH - 1); // 100000
$max = pow(10, self::OTP_LENGTH) - 1; // 999999
return (string) wp_rand($min, $max);
}
}
new WP_Minimal_Email_2FA();
2) Crea la pagina per inserire l’OTP
- Vai in Pagine → Aggiungi nuova e crea “2FA”.
- Nel contenuto inserisci lo shortcode:
[wp_2fa] - Pubblica la pagina e verifica che sia raggiungibile in
/2fa/(o l’URL scelto).
3) Test rapido
- Esci dall’admin e prova a fare login con un account valido.
- Dovresti vedere un messaggio che ti invita a inserire il codice. Apri l’email, copia l’OTP, vai su
/2fa/e conferma. - Al successo verrai reindirizzato alla Bacheca.
Hardening e miglioramenti consigliati
- Rate limiting: aggiungi un contatore tentativi OTP (es. con
user_meta) e blocca per alcuni minuti dopo N errori. - Scadenze brevi: 5–10 minuti sono un buon compromesso per l’OTP.
- IP binding: opzionale, ma puoi controllare l’IP presente nel
payloadper ridurre i rischi di hijacking di token. - HTTPS obbligatorio: assicurati che il sito usi sempre TLS.
- Backup codes: fornisci codici di recupero monouso per gli amministratori.
- Audit: registra login riusciti/respinti con data, IP e user agent.
Alternative più robuste (TOTP/WebAuthn)
Per produzione, preferisci un plugin che offra TOTP e WebAuthn. Se vuoi estendere il codice personalizzato:
- TOTP: genera e salva un secret per utente, mostra un QR con schema
otpauth://, verifica i codici con una libreria TOTP. - WebAuthn: integra registrazione e autenticazione di chiavi FIDO2. Richiede una libreria dedicata lato server.
Bozza di generazione URL TOTP (concettuale)
<?php
// ESEMPIO CONCETTUALE (usa una libreria TOTP in produzione)
$user = wp_get_current_user();
$issuer = rawurlencode(get_bloginfo('name'));
$account = rawurlencode($user->user_email);
$secret = 'JBSWY3DPEHPK3PXP'; // genera e salva un secret Base32 per l'utente
$otpauth = sprintf('otpauth://totp/%s:%s?secret=%s&issuer=%s&digits=6&period=30&algorithm=SHA1',
$issuer,
$account,
$secret,
$issuer
);
// Mostra QR con una libreria/servizio QR e salva $secret in user_meta
update_user_meta($user->ID, 'totp_secret', $secret);
Checklist di rollout
- Abilita 2FA prima sugli account admin, poi sugli editor, infine sugli autori.
- Prepara una guida per gli utenti con screenshot e domande frequenti.
- Configura codici di backup e una procedura di sblocco (su ticket, solo dopo verifica identità).
- Monitora gli errori di login per la prima settimana e misura l’adozione.
WP-CLI e automatizzazione
Alcuni plugin espongono comandi WP-CLI per forzare il 2FA su ruoli/utenti. Se disponibile, puoi abilitare in blocco:
# Esempio generico (varia in base al plugin scelto)
wp twofactor enforce --role=administrator --require=totp,email --grace=7
Domande frequenti
E se un utente perde l’accesso al secondo fattore? Usa i codici di backup o una procedura di recupero gestita dall’amministratore. Evita di disabilitare il 2FA permanentemente.
Il 2FA rallenta il login? Un po’, ma per gli amministratori il beneficio in sicurezza è enorme; per gli autori valuta Email OTP o “remember this device”.
Conclusione
Per un sito produttivo la scelta migliore è un plugin 2FA maturo con TOTP e WebAuthn, enforcement per ruolo e codici di backup. L’esempio OTP via email qui sopra è utile come base, fallback o per ambienti interni: semplice, trasparente e senza dipendenze, ma meno robusto dei metodi moderni.