https://d226lax1qjow5r.cloudfront.net/blog/blogposts/implement-multi-factor-authentication-in-go-with-verify/blog_go_verify2_1200x600.png

Implantar la autenticación multifactor en Go con Verify

Publicado el November 13, 2020

Tiempo de lectura: 11 minutos

Tanto si trabaja en un proyecto con millones de usuarios como si se trata de un pequeño proyecto secundario, garantizar que las aplicaciones que crea son seguras es de suma importancia. Applications that are not secure could expose user data to hackers, resulting in a loss of money and trust. El trabajo de un desarrollador de software es asegurarse de que el código que se escribe tiene un enfoque de seguridad en primer lugar, teniendo en cuenta todos los cabos sueltos. Una forma de garantizar la seguridad de sus aplicaciones es integrar un sistema de autenticación multifactor, comúnmente conocido como MFA.

La autenticación multifactor se utiliza para añadir seguridad adicional a las aplicaciones, así como para verificar la identidad de un usuario. En este artículo, aprenderemos a implementar un sistema MFA utilizando el lenguaje de programación Go y la Verify API de Vonage.

Requisitos previos

Para seguir este artículo, necesitarás:

  • Go (versión 1.14 o superior)

  • Módulos Go habilitados

  • Un editor de texto de su elección

  • Conocimientos básicos de Go

Cuenta API de Vonage

Para completar este tutorial, necesitarás una cuenta API de Vonage. Si aún no tienes una, puedes inscribirte hoy y comenzar a acumular crédito gratis. Una vez que tengas una Account, puedes encontrar tu clave y secreto de API en la parte superior del Panel de API de Vonage.

Configuración del proyecto

Vamos a crear una aplicación sencilla que genere un código de verificación y lo envíe a un número de teléfono proporcionado por el usuario. El usuario introduce el código en una página de confirmación y la aplicación confirmará si es válido.

App workflowApp workflow

La imagen anterior es un esbozo del flujo de trabajo de la aplicación que crearemos en este artículo.

Para empezar, crea los archivos y carpetas que coincidan con la estructura de árbol que aparece a continuación:

├── static/ │ ├── index.html │ ├── form.html ├── .env ├── utils/ │ ├── verify.go └── server.go

La carpeta static contiene dos páginas HTML (formularios) que se utilizarán para recoger el número de teléfono y el código de confirmación del usuario, respectivamente.

Los archivos Env se utilizan para almacenar variables de entorno y valores que deben mantenerse privados. El archivo .env contendrá la clave y el secreto de la API de Vonage que puedes obtener del tablero.

El directorio utils contiene un archivo verify.go que contendrá todo el código directamente relacionado con Vonage Verify API. El archivo server.go en la raíz de nuestro proyecto contendrá la lógica relacionada con el servidor que sirve los archivos HTML.

Configurar las vistas

El primer paso en la creación de nuestra aplicación web es crear un servidor web simple que servirá los formularios HTML. En el archivo server.go incluya lo siguiente:

func main() {
//Create the fileServer Handler
   fileServer := http.FileServer(http.Dir("./static"))
//Create a New Serve Mux to register handler
   mux := http.NewServeMux()
   mux.Handle("/", fileServer)
//Create the server on Port 8080 and print start message!
   fmt.Printf("Starting server at port 8080\n")
   log.Fatal(http.ListenAndServe(":8080", mux))
}

Importamos el archivo net/http package e invocamos su función FileServer pasando la ruta al directorio static como primer argumento. La función http.FileServer devuelve un handler que registramos en un patrón URL. Para registrar un handler, tenemos que crear un nuevo serve mux usando la función http.NewServeMux e invocar su método Handle pasando / como nuestro patrón junto con el manejador del servidor de archivos que generamos anteriormente. A continuación, creamos el servidor usando el método http.ListenAndServe para crear el servidor. Pasamos el puerto y el serve mux que creamos anteriormente, luego imprimimos un mensaje en la terminal para notificarnos que el servidor está funcionando.

Cuando ejecute el archivo server.go obtendrá un mensaje de inicio indicando que el servidor se ha iniciado. Sin embargo, si visita localhost:8080/ en el navegador sólo verás una página en blanco. Esto se debe a que nuestro index.html está vacío.

En el index.html filecree un formulario titulado Registro con un campo para introducir el número de teléfono del usuario y un botón de envío:

<form method="POST" action="/form">
   <h2>Register</h2>
   <label>Please Input your phone number:</label><br/>
   <input name="phone" type="tel" required value="" >
   <input type="submit" value="Submit">
</form>

El método del formulario es POST ya que el servidor estará recibiendo los datos que el usuario proporcione. Establecemos la acción del formulario a una ruta, /formque utilizaremos para procesar los datos devueltos por el formulario de registro.

Ahora que la primera página de registro está lista, podemos crear una segunda página para la confirmación del código que se envía al usuario. La página de confirmación es similar a la página de registro, con sólo unos pocos cambios en las etiquetas de los campos y entradas:

<form method="POST" action="/confirm">
   <h2>Confirm Phone Number</h2>
   <label for="phonenum">Please Input the confirmation code sent to your phone:</label><br/>
   <input id="phonenum" name="confirmation" type="tel" required value="" >
   <input name="phone" type="hidden" value="{{ .Phone }}">
   <input name="requestId" type="hidden" value="{{ .Id }}">
   <input type="submit" value="Submit">
</form>

El texto completo de ambos archivos HTML puede consultarse en el archivo repositorio de muestras. Después de configurar ambos archivos HTML, reinicie el servidor. Si todo ha ido bien, el archivo index.html debería aparecer cuando visite localhost:8080 en el navegador.

Registration formRegistration form

Si introduce un número de teléfono y pulsa el botón de envío, observará que se redirige a localhost:8080/form tal y como se especificó anteriormente con el atributo HTML action. La ruta /form no está configurada actualmente para servir el archivo form.html archivo. En la siguiente sección, crearemos un manejador para la ruta /form que aceptará el número de teléfono como argumento y mostrará el archivo confirm.html archivo.

Implantar la autenticación multifactor

En esta sección, discutiremos el núcleo de nuestra aplicación, la autenticación de múltiples factores, y la codificaremos. Utilizaremos la Verify API de Vonage para implementar la autenticación multifactor.

El proceso de implantación de la AMF con Verify API es un proceso bidireccional:

  • El primer paso es iniciar una solicitud de Verify. En esta fase se envía un código al teléfono del usuario.

  • El segundo paso consiste en comprobar si el código de verificación facilitado por el usuario es correcto.

Cuando se inicie una solicitud de Verify, se generará automáticamente un Request_id se generará automáticamente a partir del número de teléfono del usuario. Este ID se utilizará en la verificación del segundo paso.

Los desarrolladores de Vonage han creado un paquete Go para interactuar con una serie de API de Vonage, incluido Verify. Para empezar, instala el paquete en tu proyecto ejecutando go get github.com/vonage/vonage-go-sdk en el terminal.

Ahora que ya lo tenemos todo listo, ¡vamos al grano!

En el archivo verify.go cree cuatro funciones:

1. crearCliente

Esta función contiene la lógica para crear un cliente. Un cliente es necesario para que podamos interactuar con la Verify API. Para crear un cliente, invocamos la función NewVerifyClient proporcionada por el paquete vonage-go que importamos anteriormente. La función NewVerifyClient requiere un conjunto de autenticación, que creamos invocando la función CreateAuthFromKeySecret introduciendo la clave API y el secreto

Es una buena práctica almacenar la información sensible como variables de entorno para evitar que caiga en malas manos. En el archivo .env creado anteriormente, añada su clave API y su secreto en el formato API_KEY=0000000.

En este punto, la función createClient tiene este aspecto:

func createClient() *vonage.VerifyClient{
   Key, _ := os.LookupEnv("API_KEY")
   Secret, _ := os.LookupEnv("API_SECRET")
   auth := vonage.CreateAuthFromKeySecret(Key, Secret)
   client := vonage.NewVerifyClient(auth)
   return client
}

2. función init

La función init está predefinida por Go, y se utiliza para inicializar nuestra aplicación. La usaremos para cargar nuestras variables de entorno desde el archivo .env antes de ejecutar el resto de nuestro código, pero primero necesitamos instalar un paquete que se utiliza habitualmente para cargar variables de entorno. En la terminal, ejecuta go get github.com/joho/godotenv' to install the package. Next, add the corresponding import. Theinitfunction will contain just a few lines of code which invokes thegodotenv` Función de carga.

func init() {
   // loads values from .env into the system
   if err := godotenv.Load(); err != nil {
      log.Print("No .env file found")
   }
}

3. Función VerStart

Esta función es donde comenzamos la solicitud de verificación. La función va a ser exportada al paquete principal, por lo que ponemos en mayúscula la primera letra de la función como es estándar en Go. En la función VerStart iniciamos la solicitud invocando el método Request en un cliente. Invocamos la función createClient que creamos anteriormente para obtener un cliente en el que podamos llamar al método Request método:

func VerStart(phoneNumber string) string{
   client := createClient()
verification, _, err := client.Request(phoneNumber, "Go-Tut MFA", vonage.VerifyOpts{
		CodeLength: 6,
	})

   if err != nil {
      log.Fatal(err)
   }
   return verification.RequestId
}

El método Request recibe tres parámetros:

  • El número de teléfono que debe verificarse

  • La marca

  • Una estructura de opciones que se utiliza para personalizar la OTP que se enviará al usuario.

En nuestra aplicación, queremos que el número de teléfono sea dinámico, por lo que lo especificamos como un parámetro de la función Request que se pasará cuando se invoque la función. El nombre de la marca es una cadena corta que indica qué marca está enviando el SMS; en este ejemplo, he utilizado "Go-tut MFA". Una vez introducidos los parámetros necesarios en el método Request podemos ocuparnos de sus valores de retorno. El método Request devuelve una respuesta de verificación que contiene un campo de estado, una respuesta http que ignoraremos y un tipo de error. Podemos devolver el estado de la respuesta de verificación y manejar el error.

4. función verCheck

Esta función alberga la lógica relacionada con la confirmación del código de verificación que introduce el usuario. Esta función es similar a la función verStart anterior:

func VerCheck(reqId, code string) string{
   client := createClient()
   response, _, err := client.Check(reqId, code)
   if err != nil {
            log.Fatal(err)
   }
   if err != nil {
      log.Fatal(err)
   }

   return response.Status
}

La función verCheck recibe un ID de solicitud, que es un argumento obligatorio para la función Check y un parámetro code que debe ser el código de confirmación enviado al usuario. Esta función devolverá un estado, que es de tipo cadena. El estado devuelto indicará si el usuario ha introducido el código de confirmación correcto. El estado de la respuesta será 0 si y sólo si el código de confirmación introducido por el usuario es correcto.

En la siguiente sección, veremos dónde deben invocarse las cuatro funciones creadas anteriormente para que nuestra aplicación funcione.

Montar la aplicación

Ahora que todas las funciones de utilidad que nuestra aplicación necesita están listas, podemos ponerlo todo junto en una aplicación de trabajo. Ya hemos renderizado nuestra página de índice, pero también tenemos que renderizar la página de confirmación que se mostrará una vez que se haga clic en el botón de envío. Hacer esto en Go es bastante diferente de otros lenguajes. Tendremos que crear una función manejadora y adjuntarla a la ruta /form que recibe los datos del formulario de registro. A continuación, crearemos una función llamada formHandler para implementar la interfaz handler de Go. A continuación parseamos el cuerpo de la petición para poder acceder a los datos del formulario desde el directorio index.html. Para ello utilizaremos el método ParseForm de la petición http.

Los datos del formulario pueden extraerse del formulario analizado utilizando el método formValue . Este método toma el nombre especificado en el atributo de entrada HTML de nuestro archivo index.html archivo. A continuación, podemos importar e invocar la función verRequest con el número de teléfono.

Para mostrar la página de confirmación cuando un usuario introduce su número de teléfono, tendremos que crear una función aparte. Aunque podemos utilizar simplemente la función http.Servefile para servir nuestro archivo form.html no es la solución ideal en nuestro caso. Además de mostrar el archivo de confirmación, también tenemos que pasar el número de teléfono del usuario y el ID de la solicitud para que se pueda utilizar para invocar la función de utilidad verCheck de arriba.

La función de renderizado es bastante básica: recibe una solicitud http, un escritor de respuesta y una interfaz. La función render utiliza el paquete http/template para analizar los archivos y ejecutarlos con los datos proporcionados (el número de teléfono del usuario y el ID de la solicitud).

func render(w http.ResponseWriter, filename string, data interface{}) {
//parse the provided file
   tmpl, err := template.ParseFiles(filename)
   if err != nil {
      log.Println(err)
   }
//execute the file
   if err := tmpl.Execute(w, data); err != nil {
      log.Println(err)
   }
}

Con la función render lista, podemos invocarla en la función formHandler pasando la ruta al archivo del formulario y nuestro mensaje. Para pasar un mensaje, simplemente creamos un struct que define los campos que queremos pasar al archivo form.html:

type Message struct {
   Phone  string
   Id     string
}

Tenemos que crear campos ocultos en el archivo form.html para recibir las variables en la estructura de mensajes añadiendo lo siguiente al formulario:

<input name="phone" type="hidden" value="{{ .Phone }}">
<input name="requestId" type="hidden" value="{{ .Id }}">

En este punto, toda la función formHandler tiene el siguiente aspecto

func formHandler (w http.ResponseWriter, r *http.Request){
//Parse the form
      if err := r.ParseForm(); err != nil {
         fmt.Fprintf(w, "ParseForm() err: %v", err)
         return
      }
//Get the value of the Input from the form
      Phone :=  r.FormValue("phone")
      Id := verify.VerStart(Phone)
      msg := &Message{
         Id: Id,
         Phone: Phone,
      }
//Render the form.
      render(w, "./static/form.html", msg)
}

Una última cosa que hay que hacer antes de probar las cosas es registrar la función formHandler en la ruta /form ruta. Añadimos sólo una línea de código para hacer esto:

mux.HandleFunc("/form", formHandler)

¡Y ya está todo listo! Puedes ejecutar el programa, introducir tu número de teléfono en la página de registro y hacer clic en enviar. Al hacer clic en el botón de envío, se te redirigirá a la página de confirmación y recibirás un SMS en tu teléfono móvil.

SMS codeSMS code

Nuestra aplicación está quedando muy bien. Lo siguiente que tenemos que hacer es gestionar la entrada del código de confirmación por parte del usuario. En el estado actual de nuestra aplicación, si un usuario introduce su código de confirmación y hace clic en enviar son redirigidos a la ruta de confirmación que no está configurado todavía.

Configuraremos el manejador de confirmación para que reciba el código de confirmación del cliente e invoque la función verCheck función util. El manejador de confirmación es bastante similar al manejador de formulario con sólo unas pocas diferencias.

Para empezar, creamos la función confirmHandler que implemente la interfaz del manejador y analice la solicitud entrante, igual que hicimos con la función formHandler function. A continuación, extraemos los valores necesarios para ejecutar la función verCheck del formulario. Hay tres valores que necesitamos extraer:

  • El número de teléfono

  • ID de la solicitud

  • El código de confirmación

Los dos primeros valores fueron pasados desde el formHandler mediante la función render y se ocultaron en el archivo form.html file. Para obtener los valores, simplemente añadimos las siguientes líneas, que ya habíamos comentado al crear la función formHandler :

Id := r.FormValue("requestId")
phone := r.FormValue("phone")
confirmation := r.FormValue("confirmation")

Ahora que hemos recibido todos los valores necesarios para el segundo paso de nuestro flujo de trabajo (verificar el código), podemos invocar el comando verCheck functionpasando el ID de la solicitud y el código de confirmación que acabamos de extraer. ¿Recuerdas cómo configuramos la función verCheck para que devuelva un estado de éxito? Podemos comprobar ese estado para ver si el código de confirmación introducido por el usuario es correcto e imprimir un mensaje de éxito o un mensaje de error en una nueva página web utilizando la función fmt.FPrint . En este punto, el confirmHandler debería tener este aspecto:

func confirmHandler(w http.ResponseWriter, r *http.Request) {
//Parse the form
   if err := r.ParseForm(); err != nil {
      fmt.Fprintf(w, "ParseForm() err: %v", err)
      return
   }
//Extract the Form values
   Id := r.FormValue("requestId")
   phone := r.FormValue("phone")
//Receive the confirmation code
   confirmation := r.FormValue("confirmation")
//Verify the Confirmation code
   response := verify.VerCheck(Id, confirmation)
//Check if the confirmation code is incorrect
   if response != "0" {
     fmt.Fprint(w,"Verification failed! Input the correct code sent to ", phone)
      return
   }

   fmt.Fprint(w,"🎉 Success! 🎉")
}

En este punto, casi hemos terminado con nuestra aplicación. Lo último que tenemos que hacer es registrar la ruta confirmHandler añadiendo la siguiente línea de código a la función principal:

mux.HandleFunc("/confirm", confirmHandler)

¡Y ya estamos listos! Si inicias tu servidor y visitas la dirección en el navegador, deberías ver el formulario de registro. Si introduces tu número de teléfono y haces clic en enviar, recibirás el código en tu teléfono móvil. También se te redirigirá automáticamente a la página de confirmación, donde podrás introducir el código. Cuando lo envíe, debería ver el éxito impreso en la página.

Conclusión

Hasta ahora hemos creado una aplicación en Go que utiliza la Verify API de Vonage y hemos aprendido sobre desarrollo web con Go. Espero que lo hayas disfrutado. El código completo de esta aplicación se puede encontrar en GitHub.

Compartir:

https://a.storyblok.com/f/270183/400x588/18f261786d/oluwatobi-okewole.png
Oluwatobi Okewole

Oluwatobi is a software developer and writer. He loves to simplify complex topics, making them easy for anyone to understand