Adding an extra layer of security to your WordPress site can help protect your website from unauthorized access. Two-Factor Authentication (2FA) is an excellent method to enhance login security. In this tutorial, we will walk through the steps to create a custom 2FA email sending plugin for WordPress.
Step 1: Plugin Setup
Create a new plugin file named brahman-2fa.php
in your WordPress wp-content/plugins
directory. Add the following header to your plugin file:
<?php
/*
* Plugin Name: Custom Email 2FA by Brahman WebTech.
* Plugin URI: https://learn.bmwtech.in/creating-a-custom-2fa-email-sending-wordpress-authentication-plugin/
* Description: Custom Email 2FA by Brahman WebTech.
* Version: 1.0.0.0
* Requires at least: 5.5
* Requires PHP: 8.0
* Author: Brahman WebTech
* Author URI: https://bmwtech.in
*/
?>
Step 2: Generate and Send OTP
We need to generate a One-Time Password (OTP) after the initial authentication and send it to the user's email. Add the following function to your plugin file:
function brahman_2fa_send_otp_after_authentication($user, $username, $password) {
if (is_wp_error($user)) {
return $user;
}
if (isset($_POST['otp'])) {
return $user;
}
$otp = rand(100000, 999999); // Generate a 6-digit OTP
update_user_meta($user->ID, 'user_otp', $otp);
// Send OTP to the user's email
$to = $user->email; //'user@example.com';
$subject = 'OTP for WordPress Login Authorization';
$message = 'Your OTP is: ' . $otp;
$headers = array('Content-Type: text/html; charset=UTF-8');
wp_mail($to, $subject, $message, $headers);
// Redirect to login page with OTP flag
wp_redirect(add_query_arg('otp_sent', 'true', wp_login_url()));
exit();
}
add_filter('authenticate', 'brahman_2fa_send_otp_after_authentication', 30, 3);
Step 3: Display OTP Input Field
Next, we need to display an input field for the OTP if it has been sent. Add the following function to your plugin file:
function brahman_2fa_login_form() {
if (isset($_GET['otp_sent'])) {
echo '
<form name="otpform" id="otpform" action="' . esc_url($_SERVER['REQUEST_URI']) . '" method="post">
<p>
<label for="otp">One Time Password<br>
<input type="text" name="otp" id="otp" class="input" value="" size="20"></label>
</p>
<p class="submit">
<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="Verify OTP">
</p>
</form>';
echo "<script>
document.addEventListener('DOMContentLoaded', function() {
const storedCredentials = window.electron.getCredentials();
if (storedCredentials.username && storedCredentials.password) {
document.getElementById('user_login').value = storedCredentials.username;
document.getElementById('user_pass').value = storedCredentials.password;
}
document.getElementById('loginform').addEventListener('submit', function() {
const username = document.getElementById('user_login').value;
const password = document.getElementById('user_pass').value;
window.electron.saveCredentials(username, password);
});
});
</script>";
exit();
}
}
add_action('login_form', 'brahman_2fa_login_form');
Step 4: Validate OTP
We need to validate the OTP entered by the user. Add the following function to your plugin file:
function brahman_2fa_validate_otp($user, $username, $password) {
if (isset($_POST['otp'])) {
$otp = $_POST['otp'];
$saved_otp = get_user_meta($user->ID, 'user_otp', true);
if ($otp !== $saved_otp) {
return new WP_Error('incorrect_otp', ('<strong>ERROR</strong>: Incorrect OTP.'));
}
// If OTP is correct, remove the OTP from user meta
delete_user_meta($user->ID, 'user_otp');
} else {
return new WP_Error('empty_otp', ('Please enter the OTP.'));
}
return $user;
}
add_filter('authenticate', 'brahman_2fa_validate_otp', 40, 3);
Step 5: Add JavaScript for ElectronJS Integration
To handle credentials inside an ElectronJS app, we need to inject JavaScript. Here's how you can do it:
function brahman_2fa_enqueue_inline_script() {
add_action('login_form', function() {
echo "<script>
document.addEventListener('DOMContentLoaded', function() {
const storedCredentials = window.electron.getCredentials();
if (storedCredentials.username && storedCredentials.password) {
document.getElementById('user_login').value = storedCredentials.username;
document.getElementById('user_pass').value = storedCredentials.password;
}
document.getElementById('loginform').addEventListener('submit', function() {
const username = document.getElementById('user_login').value;
const password = document.getElementById('user_pass').value;
window.electron.saveCredentials(username, password);
});
});
</script>";
});
}
add_action('init', 'brahman_2fa_enqueue_inline_script');
Step 6: Secure Local Storage Texts with CryptoJS
To keep local storage texts secure in ElectronJs, we use CryptoJS. Here is a brief summary of how you can do it. For more details, refer to the full blog post.
Encrypting Data
const CryptoJS = require('crypto-js');
function encryptData(data, secretKey) {
return CryptoJS.AES.encrypt(JSON.stringify(data), secretKey).toString();
}
function decryptData(ciphertext, secretKey) {
const bytes = CryptoJS.AES.decrypt(ciphertext, secretKey);
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
}
// Example usage:
const secretKey = 'your-secret-key';
const encryptedData = encryptData({ username: 'user', password: 'pass' }, secretKey);
localStorage.setItem('credentials', encryptedData);
const storedData = localStorage.getItem('credentials');
const decryptedData = decryptData(storedData, secretKey);
console.log(decryptedData);
Step 7: Debug and Log User Data
For debugging purposes, you can add logging to check user roles and capabilities:
function brahman_2fa_debug($user, $username, $password) {
if (!is_wp_error($user)) {
error_log('User ID: ' . $user->ID);
error_log('User Roles: ' . implode(', ', $user->roles));
error_log('User Capabilities: ' . print_r($user->allcaps, true));
error_log('User Status: ' . get_user_meta($user->ID, 'user_status', true));
}
return $user;
}
add_filter('authenticate', 'brahman_2fa_debug', 50, 3);
The Complete Code
<?php /* Plugin Name: Brahman WebTech Email 2FA for WordPress Description: A custom email 2FA implementation for WordPress. Version: 1.0 Author: Brahman WebTech */ // Step 2: Generate and Send OTP function brahman_2fa_send_otp_after_authentication($user, $username, $password) { if (is_wp_error($user)) { return $user; } if (isset($_POST['otp'])) { return $user; } $otp = rand(100000, 999999); // Generate a 6-digit OTP update_user_meta($user->ID, 'user_otp', $otp); // Send OTP to the user's email $to = $user->email; //'user@example.com'; $subject = 'OTP for WordPress Login Authorization'; $message = 'Your OTP is: ' . $otp; $headers = array('Content-Type: text/html; charset=UTF-8'); wp_mail($to, $subject, $message, $headers); // Redirect to login page with OTP flag wp_redirect(add_query_arg('otp_sent', 'true', wp_login_url())); exit(); } add_filter('authenticate', 'brahman_2fa_send_otp_after_authentication', 30, 3); // Step 3: Display OTP Input Field function brahman_2fa_login_form() { if (isset($_GET['otp_sent'])) { echo ' <form name="otpform" id="otpform" action="' . esc_url($_SERVER['REQUEST_URI']) . '" method="post"> <p> <label for="otp">One Time Password<br> <input type="text" name="otp" id="otp" class="input" value="" size="20"></label> </p> <p class="submit"> <input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="Verify OTP"> </p> </form>'; echo "<script> document.addEventListener('DOMContentLoaded', function() { const storedCredentials = window.electron.getCredentials(); if (storedCredentials.username && storedCredentials.password) { document.getElementById('user_login').value = storedCredentials.username; document.getElementById('user_pass').value = storedCredentials.password; } document.getElementById('loginform').addEventListener('submit', function() { const username = document.getElementById('user_login').value; const password = document.getElementById('user_pass').value; window.electron.saveCredentials(username, password); }); }); </script>"; exit(); } } add_action('login_form', 'brahman_2fa_login_form'); // Step 4: Validate OTP function brahman_2fa_validate_otp($user, $username, $password) { if (isset($_POST['otp'])) { $otp = $_POST['otp']; $saved_otp = get_user_meta($user->ID, 'user_otp', true); if ($otp !== $saved_otp) { return new WP_Error('incorrect_otp', ('<strong>ERROR</strong>: Incorrect OTP.')); } // If OTP is correct, remove the OTP from user meta delete_user_meta($user->ID, 'user_otp'); } else { return new WP_Error('empty_otp', ('Please enter the OTP.')); } return $user; } add_filter('authenticate', 'brahman_2fa_validate_otp', 40, 3); // Step 5: Add JavaScript for ElectronJS Integration function brahman_2fa_enqueue_inline_script() { add_action('login_form', function() { echo "<script> document.addEventListener('DOMContentLoaded', function() { const storedCredentials = window.electron.getCredentials(); if (storedCredentials.username && storedCredentials.password) { document.getElementById('user_login').value = storedCredentials.username; document.getElementById('user_pass').value = storedCredentials.password; } document.getElementById('loginform').addEventListener('submit', function() { const username = document.getElementById('user_login').value; const password = document.getElementById('user_pass').value; window.electron.saveCredentials(username, password); }); }); </script>"; }); } add_action('init', 'brahman_2fa_enqueue_inline_script'); // Step 7: Debug and Log User Data function brahman_2fa_debug($user, $username, $password) { if (!is_wp_error($user)) { error_log('User ID: ' . $user->ID); error_log('User Roles: ' . implode(', ', $user->roles)); error_log('User Capabilities: ' . print_r($user->allcaps, true)); error_log('User Status: ' . get_user_meta($user->ID, 'user_status', true)); } return $user; } add_filter('authenticate', 'brahman_2fa_debug', 50, 3); ?>
Conclusion
By following these steps, you can create a custom 2FA email sending authentication plugin for WordPress. This adds an extra layer of security to your login process by requiring users to enter a One-Time Password (OTP) sent to their email. Additionally, securing local storage texts with CryptoJS in your ElectronJs app ensures sensitive information remains protected.
Feel free to customize and enhance this plugin on GitHub based on your specific requirements. Happy coding!
Leave a Reply