Creating a Custom 2FA Email Sending WordPress Authentication Plugin

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!

0 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *


You may like to read


Follow Us

Sponsored Ads

GranNino Ads

Newsletter

Subscribe to our Newsletter to read our latest posts at first

We would not spam your inbox! Promise
Get In Touch

© Fullstack Coding Tips. All Rights Reserved.