https://d226lax1qjow5r.cloudfront.net/blog/blogposts/adding-2-factor-authentication-to-wordpress-with-nexmo-verify-api-dr/E_2FA-WordPress_1200x600.jpg

Añadir autenticación de 2 factores a WordPress con Nexmo Verify API

Publicado el May 13, 2021

Tiempo de lectura: 9 minutos

Introducción

Con las brechas de seguridad en aumento, la protección de su sitio web y sus usuarios nunca ha sido tan importante como lo es hoy. En este tutorial, configurará 2FA (autenticación de dos factores) en WordPress utilizando Nexmo Verify API. Con esta configuración, cada vez que un usuario intente iniciar sesión, recibirá un SMS o una llamada al número de móvil registrado en su perfil con un código único, y al introducir el código válido se iniciará la sesión.

Al final de este tutorial, usted habrá aprendido los conceptos básicos de la construcción de un plugin de WordPress y tener una buena comprensión de cómo integrar la API Nexmo con WordPress utilizando la Biblioteca PHP Nexmo. ¡Así que vamos a empezar!

Acerca de Nexmo Verify API

Verify API es la solución plug and play de Nexmo para la autenticación de dos factores en cualquier sistema. El proceso es sencillo; se llama a la función Enviar verificación pasando un número de móvil, Nexmo enviará al número de móvil un código único a través de SMS/llamada y devolverá un ID de solicitud. Con el código del usuario, se llama a la función Comprobar verificación junto con el ID de solicitud para verificar el código, y Nexmo devuelve un estado dependiendo de si el código es válido o no. Muy sencillo, ¿verdad?

Me gusta especialmente Nexmo Verify por algo llamado flujos de trabajo. Con los flujos de trabajo, puede configurar múltiples opciones en caso de que un usuario no reciba el código a través de SMS a tiempo. Puede configurar una opción de respuesta para una llamada de voz después de una cierta duración y mucho más, puede leer acerca de los flujos de trabajo aquí.

Lo que necesitas para empezar:

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Creación del plugin de WordPress

Aunque puedes crear un plugin de WordPress simplemente añadiendo una nueva carpeta en la carpeta de plugins de WordPress, una forma realmente eficiente es usar un boilerplate ligero para empezar. Los Boilerplates son útiles para configurar todos los archivos y Clases de una manera estándar. En este tutorial, usarás la plantilla wppb simplemente rellene los detalles en la página web, y haga clic en el botón de construir plugin para descargar su plugin boilerplate.

Wordpress Plugin Boilerplate GeneratorWordpress Plugin Boilerplate Generator

Cuando lo tengas, extrae el archivo zip y colócalo en la carpeta de plugins de WordPress. A continuación, ve al administrador de WordPress, navega hasta la página de plugins instalados y activa el plugin.

PluginsPlugins

Historias de usuarios

Con nuestro objetivo de configurar 2FA en WordPress, vamos a dividir la idea en historias/pasos de usuario procesables e implementarlos de arriba a abajo:

  • En la configuración del perfil de usuario de WordPress, necesitará campos personalizados para el número de móvil 2FA del usuario y si están habilitados para 2FA

  • Enviarás un código al número de móvil del usuario cuando se conecte

  • Tendrás un formulario para que el usuario introduzca el código que ha recibido y verifique el código

Acciones y filtros de WordPress

La mayoría de las modificaciones que realizará en WordPress tienen que ver con la conexión de sus funciones a acciones y filtros existentes en WordPress; Acciones son funciones que se ejecutan cuando ocurre un determinado evento en WordPress. Por lo tanto, si desea que su función se ejecute cuando se produce un evento específico, por ejemplo, cuando un usuario inicia sesión, enganchará su función a la acción relacionada con el inicio de sesión. En el otro extremo, Filtros modifican ciertas funciones; puede usar un filtro para manipular el resultado de una función.

Engancha tu función a una acción existente de esta manera:

add_action( 'action_name', 'name_of_your_function' );

Usted engancha su función a un filtro como este:

add_filter( 'filter_name', 'name_of_your_function' );

Campos personalizados de usuario

Para abreviar este post, aquí está el enlace al código que añade los campos de número de móvil y casilla de verificación 2FA habilitada a la configuración del perfil de usuario de WordPress. Con esa configuración, la página de configuración de usuario se vería así:

Nexmo 2FANexmo 2FA

Añadir la librería Nexmo PHP a la mezcla

Nexmo recomienda instalar su Biblioteca PHP a través de Composer; necesitarás tener Composer instalado en tu servidor/sistema local. Si no tienes Composer instalado, aquí hay dos guías recomendadas por Nexmo para ayudarte: GetComposer.org o Scotch.io

Con Composer instalado, abre tu terminal, navega a la carpeta raíz de tu plugin, y ejecútalo:

composer require nexmo/client

Creará una nueva carpeta llamada vendor que contiene un número de otras librerías, incluyendo la librería PHP de Nexmo. Para incluir la librería en tu plugin, simplemente añade esta línea al archivo raíz PHP de tu plugin.

require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload.php';

Inicialización de la clase Nexmo

Antes de poder realizar cualquier llamada con la librería PHP de Nexmo, necesitas inicializar la Clase Nexmo y utilizar el objeto cliente para realizar tus peticiones posteriores.

$basic  = new \Nexmo\Client\Credentials\Basic(NEXMO_API_KEY, NEXMO_API_SECRET);
$client = new \Nexmo\Client(new \Nexmo\Client\Credentials\Container($basic));

Si está utilizando la plantilla, puede realizar las siguientes modificaciones en el archivo archivo de clase pública en la carpeta /public de tu plugin. Estarás inicializando la Biblioteca Nexmo en la construcción y estableciendo el objeto cliente como una variable protegida para que otros métodos de nuestra Clase pública puedan acceder a ella.

<?php
 
 
class Two_Factor_Auth_Nexmo_Public {
 
    protected $nexmo_client;
 
    public function __construct( $plugin_name, $version ) {
       $this->nexmo_client = new Nexmo\Client(new Nexmo\Client\Credentials\Basic(TWO_FACTOR_AUTH_NEXMO_KEY, TWO_FACTOR_AUTH_NEXMO_SECRET));
       
    }
}

Envío de la solicitud de verificación

Basándonos en nuestra historia de usuario, queremos interceptar el login del usuario, comprobar si el usuario está habilitado para 2FA, y si lo está, iniciar un comando Enviar Verificación a su número de móvil. Para conseguir esto, engancharemos la acción de authenticate de WordPress, ya que es la acción que se activa cuando un usuario inicia sesión correctamente.

<?php
 
class Two_Factor_Auth_Nexmo_Public {
 
    protected $nexmo_client;
 
    public function __construct( $plugin_name, $version ) {
        $this->nexmo_client = new Nexmo\Client(new Nexmo\Client\Credentials\Basic(TWO_FACTOR_AUTH_NEXMO_KEY, TWO_FACTOR_AUTH_NEXMO_SECRET));
        
 
        add_action( 'authenticate', array( $this, 'intercept_login_with_two_factor_auth' ), 10, 3 );
    }
 
    public function intercept_login_with_two_factor_auth( $user, $username, $password ) {
        $errors = array();
        $redirect_to = isset( $_POST['redirect_to'] ) ? $_POST['redirect_to'] : admin_url();
        $remember_me = ( isset( $_POST['rememberme'] ) && $_POST['rememberme'] === 'forever' ) ? true : false;
        
        $_user = get_user_by( 'login', $username );
 
        if ( $_user ) {
            $this->verify_user( $_user, $redirect_to, $remember_me );
        }
 
        return $user;
    }

Enganchando nuestra función a la acción authenticate nos da tres variables con las que trabajar: $user, $usernamey $passwordla variable $user es normalmente nula, pero no pasa nada. Lo que necesitas es la variable $usernamey con eso, puedes llamar a la función de WordPress para obtener el objeto usuario. Necesitas el objeto de usuario para obtener otras propiedades del usuario como el número de móvil y si está habilitado para 2FA.

$_user = get_user_by( 'login', $username );// Function to get the user object

Para obtener el valor de los campos personalizados de un usuario, utilice la función de WordPress get_user_metapasando el ID de usuario y el nombre del campo personalizado. Tal y como se define en el código de configuración del perfil de usuario, los nombres de los campos son two_factor_auth_nexmo_enabled y two_factor_auth_nexmo_mobile.

  private function verify_user( $user, $redirect_to, $remember_me, $errors = array() ) {
        
        $enabled_2fa = get_user_meta($user->ID, 'two_factor_auth_nexmo_enabled', true );
        $mobile = get_user_meta($user->ID, 'two_factor_auth_nexmo_mobile', true );
        
        if ( ! $mobile || ! $enabled_2fa) {
            return;
        }
 
        try {
            $verification = new \Nexmo\Verify\Verification($mobile, TWO_FACTOR_AUTH_NEXMO_SENDER_NAME);
            $this->nexmo_client->verify()->start($verification);
        }
        catch(Exception $e) {
            $errors = array( "Error sending verification request" );
        }
        
 
        $request_id = $verification->getRequestId();
        update_user_meta( $user->ID, 'two_factor_auth_nexmo_request_id', $request_id);
        
        wp_logout();
        
    }

Si se establecen todas las variables requeridas (número de móvil y 2FA activado para el usuario), se llama a la función para iniciar un Enviar verificación Nexmo envía al usuario el código único, y se devuelve un ID de solicitud. Es importante almacenar el ID de solicitud para que pueda utilizarlo para llamar a la función Validar código en el siguiente paso. Una forma sencilla de hacerlo es utilizar la función de WordPress update_user_meta con el identificador clave de la variable (en nuestro caso two_factor_auth_nexmo_request_id) y la variable $request_id.

Por último, llame a la función logout función. Te estarás preguntando, ¿por qué harías eso? Lo haces porque, como se indicó inicialmente, la acción de autenticación se activa cuando el usuario ha iniciado sesión correctamente. Así que, en realidad, todo este código se ejecuta cuando el usuario ha iniciado sesión, pero eso no es lo que quieres, al menos por ahora. Usted quiere que el usuario inicie sesión sólo después de validar el código. Por lo tanto, cerrar la sesión del usuario después de enviar el código, y que nos lleva a nuestro último paso, validar el código y el inicio de sesión del usuario.

Validación del código

Ya has enviado el código al usuario en la sección anterior, sólo queda mostrarle un formulario para que introduzca el código que ha recibido y validarlo con su perfil para iniciar sesión.

Basándose en la función verify_user que creó anteriormente, agregará el formulario de validación de código.

private function verify_user( $user, $redirect_to, $remember_me, $errors = array() ) {
        
        $enabled_2fa = get_user_meta($user->ID, 'two_factor_auth_nexmo_enabled', true );
        $mobile = get_user_meta($user->ID, 'two_factor_auth_nexmo_mobile', true );
        
        if ( ! $mobile || ! $enabled_2fa) {
            return;
        }
 
        try {
            $verification = new \Nexmo\Verify\Verification($mobile, TWO_FACTOR_AUTH_NEXMO_SENDER_NAME);
            $this->nexmo_client->verify()->start($verification);
        }
        catch(Exception $e) {
            $errors = array( "Error sending verification request" );
        }
        
 
        $request_id = $verification->getRequestId();
        update_user_meta( $user->ID, 'two_factor_auth_nexmo_request_id', $request_id);
        
        wp_logout();
        nocache_headers();
        header('Content-Type: ' . get_bloginfo( 'html_type' ) . '; charset=' . get_bloginfo( 'charset' ) );
        login_header('Nexmo Two-Factor Authentication', '<p class="message">' . sprintf( 'Enter the PIN code sent to your mobile number ending in <strong>%1$s</strong>' , substr($mobile, -5) ) . '</p>');
 
        if(!empty($errors)) { 
        
        ?>
            <div id="login_error"><?php echo implode( '<br />', $errors ) ?></div>
        <?php  } ?>
 
        <form name="loginform" id="loginform" action="<?php echo esc_url( site_url( 'wp-login.php', 'login_post' ) ) ?>" method="post" autocomplete="off">
            <p>
                <label for="two_factor_auth_nexmo_pin_code">Code
                    <br />
                    <input type="number" name="two_factor_auth_nexmo_pin_code" id="two_factor_auth_nexmo_pin_code" class="input" value="" size="6" />
                </label>
            </p>
            <p class="submit">
                <input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="Verify" />
                <input type="hidden" name="log" value="<?php echo esc_attr( $user->user_login ) ?>" />
                <input type="hidden" name="two_factor_auth_nexmo_request_id" value="<?php echo esc_attr( $request_id ) ?>" />
                <input type="hidden" name="redirect_to" value="<?php echo esc_attr( $redirect_to ) ?>" />
 
                <?php if ( $remember_me ) : ?>
                    <input type="hidden" name="rememberme" value="forever" />
                <?php endif; ?>
            </p>
        </form>
 
        <?php 
        
        login_footer( 'two_factor_auth_nexmo_pin_code' );
 
        exit;
    }

NB: Tengo mi HTML en el método de clase aquí, pero siempre se puede poner la suya en un archivo separado e incluirlo.

Con el código anterior, deberías tener una página parecida a esta cuando intentas entrar en una Account habilitada para 2FA.

Wordpress PinWordpress Pin

La función login_header añade esa pequeña sección encima del formulario que muestra los 5 últimos dígitos del número de móvil del usuario.

El <form> en esta vista es una réplica del formulario de inicio de sesión de WordPress con sólo un par de cambios. Si inspecciona su formulario de inicio de sesión de WordPress a través de su navegador, notará que el campo de entrada de nombre de usuario tiene un atributo de nombre llamado "<strong>log</strong>". Consérvalo porque así es como WordPress obtiene la variable nombre de usuario; sin embargo, en este caso, pasarás el nombre de usuario desde tu objeto usuario. Para mantener la experiencia de las redirecciones y la preferencia remember me de la sesión de inicio de sesión, mantenga esas variables en sus respectivos nombres de campo de entrada, pero esta vez, estarán ocultas. Por último, pase el ID de solicitud de la llamada a la solicitud de envío de verificación en un campo de entrada oculto, así como el valor del código que introduzca el usuario: todo ello se utilizará para validar el código.

Los parámetros del formulario son:

  • log: Nombre de usuario del usuario

  • two_factor_auth_nexmo_pin_code: Código que introduce el usuario

  • two_factor_auth_nexmo_request_id: ID de la solicitud inicial

  • rememberme: Recordarme desde el inicio de sesión

  • redirect_to: Redirigir a URL si el usuario estaba intentando acceder a una página concreta antes del formulario de inicio de sesión.

Nota importante: La URL de acción del formulario sigue siendo la ruta URL de inicio de sesión de WordPress <?php echo esc_url( site_url( 'wp-login.php', 'login_post' ) ) ?>Lo interesante es que, dado que nuestra función de intercepción está conectada a la acción de autenticación, cuando se hace clic en el botón de verificación del formulario de verificación de código, la función de intercepción se sigue ejecutando y es ahí donde se añade el fragmento de código para validar el código introducido por el usuario.

public function intercept_login_with_two_factor_auth( $user, $username, $password ) {
        $errors = array();
        $redirect_to = isset( $_POST['redirect_to'] ) ? $_POST['redirect_to'] : admin_url();
        $remember_me = ( isset( $_POST['rememberme'] ) && $_POST['rememberme'] === 'forever' ) ? true : false;
        
        $_user = get_user_by( 'login', $username );
// New addition
        $saved_request_id =  ($_user) ? get_user_meta($_user->ID, 'two_factor_auth_nexmo_request_id', true ) : null;
        $nexmo_pin_code = isset( $_POST['two_factor_auth_nexmo_pin_code'] ) ? $_POST['two_factor_auth_nexmo_pin_code'] : false;
        $nexmo_request_id = isset( $_POST['two_factor_auth_nexmo_request_id'] ) ? $_POST['two_factor_auth_nexmo_request_id'] : false;
        
        if ( $nexmo_request_id && $nexmo_pin_code && $saved_request_id == $nexmo_request_id ) {
            
            $verification = new \Nexmo\Verify\Verification($nexmo_request_id);
            try {
                $result = $this->nexmo_client->verify()->check($verification, $nexmo_pin_code);
                $response = $result->getResponseData();
                if ($response['status']  == "0") {
                    wp_set_auth_cookie( $_user->ID, $remember_me );
                    wp_safe_redirect( $redirect_to );
                    exit;
                }
            }
            catch(Exception $e) {
                // handle invalid  code
                if ($e->getCode() == 16){
                    $errors = array( "Invalid PIN code" );
                }
                $this->verify_user( $_user, $redirect_to, $remember_me,$errors );
            }
        }
// End of addition
        if ( $_user ) {
            $this->verify_user( $_user, $redirect_to, $remember_me );
        }
 
        return $user;
    }

En la adición anterior, está comprobando los datos POST de la solicitud de inicio de sesión para ver si el campo 'two_factor_auth_nexmo_pin_code' y 'two_factor_auth_nexmo_request_id' están activadas. Si lo están, se trata de una petición para validar un código. Para asegurarte de que estás validando el código para el usuario correcto, también comparas el ID de solicitud de los datos POST con el ID de solicitud guardado en el perfil del usuario; si coinciden, entonces llamas a la función Comprobar verificación para verificar que el código introducido es correcto. Si es correcto, se establece la cookie de autenticación de WordPress para el usuario y se le redirige al panel de control.

wp_set_auth_cookie( $_user->ID, $remember_me );
wp_safe_redirect( $redirect_to );

Si no lo es, basta con llamar de nuevo a la función verify_user función de nuevo y enviar un nuevo código de verificación.

Aquí hay un enlace al archivo de clase completo.

Conclusión

En este tutorial, usted aprendió los conceptos básicos de la construcción de un plugin de WordPress, la integración de la Biblioteca PHP Nexmo, y el uso de Nexmo Verify API. Espero que haya encontrado esto muy útil, puede consultar el código completo en GitHub a través de este enlace. Gracias por leer.

Compartir:

https://a.storyblok.com/f/270183/384x384/6293d5e6de/douglaskendyson.png
Douglas Kendyson

Douglas is a full stack software engineer with a keen interest to marketing, customer growth and data analytics. When he’s not coding, he’s probably watching way too many TV shows.