
Compartir:
Antiguo redactor técnico en Vonage. Le encanta jugar con las API y documentarlas.
Añada 2FA a su aplicación con Verify API y Golang SDK
Tiempo de lectura: 16 minutos
Cuando has trabajado duro en la construcción de una aplicación web que ofrece un valor real a tus usuarios, puede ser realmente descorazonador ver cómo se abusa de ella. Credenciales filtradas, registros falsos... siempre hay una pequeña minoría que intenta utilizar tu plataforma para sus propios fines nefastos.
Aunque es casi imposible evitar que esto ocurra en algún nivel, puede disuadir a todos los abusadores, excepto a los más comprometidos, implantando la autenticación de doble factor (2FA).
¿Qué es la 2FA?
2FA es una capa adicional de protección que requiere que el usuario proporcione algo más que un nombre de usuario y una contraseña para utilizar su servicio. Por lo general, se trata del acceso a un dispositivo móvil que puede recibir un código de seguridad que luego introduce en su aplicación como parte del proceso de registro o inicio de sesión. Como tu número de teléfono es único, a los estafadores les resulta mucho más difícil hacerse pasar por usuarios existentes o registrar varias cuentas falsas.
Otro buen caso de uso para 2FA es la "autenticación por pasos". Esto es ideal para situaciones en las que un usuario intenta hacer algo en su aplicación que podría tener malas consecuencias si ese usuario no es quien dice ser. Por ejemplo, si tiene una aplicación bancaria y su usuario intenta añadir un nuevo beneficiario. En casos como este, 2FA proporciona cierta seguridad de que son legítimos.
La Verify API de Vonage
La página Verify API de Vonage hace que sea realmente sencillo implementar 2FA en tus aplicaciones y eso es lo que esperamos demostrar en el post de hoy. Construiremos un sitio simple para una compañía ficticia llamada Acme Inc, que requiere que los usuarios se registren usando su dispositivo móvil para agregar cierto nivel de protección contra el uso fraudulento.
Y, para hacer las cosas más emocionantes, vamos a construir esto en nuestro nuevo (beta) Go SDK. Esto debería complacer a todos los Gophers endurecidos por ahí y tal vez incluso animar a otros a tener su primera experiencia de desarrollo Go? (Alerta de spoiler: creo que te encantará Go).
¡Manos a la obra! (Si no tienes tiempo, puedes encontrar el código fuente en el código fuente en GitHub).
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.
Cree su proyecto
Crea un directorio para tu proyecto y dentro de él, ejecuta go mod init. Esto genera un archivo go.mod para gestionar las dependencias.
A continuación, cree un main.go archivo. Aquí es donde escribirás el código de tu aplicación:
Vamos a utilizar plantillas Go para construir nuestro sitio. Vamos a crear un directorio para ellas (tmpl) y también para el CSS que usaremos para dar estilo a nuestras páginas (static):
Configurar el SDK Vonage Go
Vamos a utilizar la Verify API para pedir a los usuarios que demuestren que son propietarios del dispositivo con el que quieren registrarse en nuestro servicio. Verify API hace que todo el proceso de autenticación de dos factores sea realmente sencillo.
No hace mucho tiempo, los desarrolladores de Go habríamos tenido que hacer todas las llamadas a la API manualmente, pero en estos tiempos más ilustrados tenemos nuestro nuevo y reluciente Vonage Go SDK para hacernos la vida más fácil, así que vamos a instalarlo y configurarlo.
Para utilizar el SDK de Go tenemos que proporcionar nuestra clave y secreto de API, que se pueden encontrar en el Panel del desarrollador. Ahora bien, no queremos poner esas credenciales directamente en nuestro código, porque cualquiera que tenga acceso a nuestra clave y secreto puede hacer llamadas a la API a nuestra costa. Así que configuraremos esos detalles en un archivo .env y usaremos el paquete github.com/joho/godotenv para leer esos valores en variables de entorno.
En primer lugar, cree el archivo .env en el directorio del proyecto:
VONAGE_API_KEY=<YOUR_API_KEY>
VONAGE_API_SECRET=<YOUR_API_SECRET>Sustituya <YOUR_API_KEY> y <YOUR_API_SECRET> por su propia clave y secreto de API.
A continuación, ejecute go get para instalar dotenv y el SDK Vonage Go:
En main.go escribe código para leer tus credenciales de la API de Vonage desde el archivo .env y utilizarlas para instanciar una instancia del SDK Vonage Go VerifyClient:
package main
import (
"log"
"os"
"github.com/joho/godotenv"
"github.com/vonage/vonage-go-sdk"
)
var verifyClient *vonage.VerifyClient
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
os.Exit(1)
}
apiKey := os.Getenv("VONAGE_API_KEY")
apiSecret := os.Getenv("VONAGE_API_SECRET")
auth := vonage.CreateAuthFromKeySecret(apiKey, apiSecret)
verifyClient = vonage.NewVerifyClient(auth)
} Construir la interfaz de usuario
Para entender qué páginas tiene que presentar nuestra aplicación web a los usuarios, consideremos el flujo de trabajo que vamos a implementar:
The verification process
A partir de aquí, puede ver que necesitamos las siguientes páginas:
La página página de inicio (
/): Si el usuario está registrado, mostramos aquí su nombre registrado y su número de teléfono.La página página de registro (
/register): Donde el usuario puede introducir su nombre y número de teléfono para registrarse en nuestro servicio.El código de verificación página de entrada (
/enter-code): Donde el usuario introduce el código PIN enviado a su teléfono por la Verify API.
Crearemos estas páginas utilizando plantillas Go. Estas facilitan el paso de variables desde el código a la página y también hacen que una plantilla incluya otras como "parciales" para que no tengas que duplicar contenido común como cabeceras, menús, etc.
En primer lugar, añada el módulo html/template a la lista de importaciones en main.go. A continuación, cree las siguientes plantillas en el directorio tmpl directorio:
La plantilla de la página base
Este es el diseño base que utilizarán todas nuestras plantillas. La directiva {{template "main" .}} nos permite rellenar el cuerpo de la página con contenido de las otras plantillas:
<!-- base.layout.gohtml-->
{{define "base"}}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Go Verify Demo</title>
<link rel="stylesheet" href="/static/style.css">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300&family=Seymour+One&display=swap" rel="stylesheet">
</head>
<body>
{{template "main" .}}
</body>
</html>
{{end}} La plantilla de la página de inicio
Cuando el usuario se haya registrado correctamente en el servicio, esta página mostrará su nombre y número de teléfono:
<!-- home.page.gohtml -->
{{template "base" .}}
{{define "main"}}
<h1>Welcome to Acme Inc {{.Name}}!</h1>
<p>Your registered phone number is: {{.Phone}}</p>
{{end}} Plantilla de la página de registro
Esta plantilla incluye un formulario en el que el usuario puede introducir su nombre y número de teléfono para registrarse en el servicio:
<!-- register.page.gohtml -->
{{template "base" .}}
{{define "main"}}
<h1>Acme inc.</h1>
<p>Please register using your mobile/cell phone number for the exciting benefits our service provides!</p>
<form action="/verify">
<fieldset>
<label for="name">Your name:</label>
<input type="text" class="ghost-input" id="name" name="name" placeholder="Jane Smith" required/>
<label for="phone_number">Your mobile number:</label>
<input type="text" class="ghost-input" id="phone_number" name="phone_number" placeholder="447700900001"required/>
<button type="submit" class="ghost-button">Register</button>
</fieldset>
</form>
{{end}}
Plantilla de la página de introducción de códigos
Esta página muestra un formulario en el que el usuario introduce el código que ha recibido de la Verify API:
<!-- entercode.page.gohtml -->
{{template "base" .}}
{{define "main"}}
<h1>Acme inc.</h1>
<p>Please enter the code you received by SMS:</p>
<form action="/check-code">
<fieldset>
<label for="pin_code">Enter PIN:</label>
<input type="text" class="ghost-input" id="pin_code" name="pin_code" placeholder="123456" required><br><br>
<button type="submit" class="ghost-button">Let's Go!</button>
</fieldset>
</form>
{{end}}
Haz que parezca bonito(s)
Por último, añadamos algo de CSS para que estas páginas parezcan que hemos invertido algún esfuerzo en su creación. (No soy una persona de front-end me temo, por lo que PRs son muy bienvenidos!)
Coge el style.css archivo del repositorio de Github e inclúyalo en su directorio static directorio.
Go necesita ser capaz de servir ese contenido CSS estático desde tu sistema de archivos local, por lo que necesitas decirle a tu aplicación cómo hacerlo.
En main.goimporte primero el módulo net/http y luego modifique su función main() como sigue:
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
os.Exit(1)
}
apiKey := os.Getenv("VONAGE_API_KEY")
apiSecret := os.Getenv("VONAGE_API_SECRET")
auth := vonage.CreateAuthFromKeySecret(apiKey, apiSecret)
verifyClient = vonage.NewVerifyClient(auth)
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
} Gestionar la sesión del usuario
Tenemos que pensar en cómo vamos a saber si un usuario se ha registrado en nuestro servicio para poder iniciar sesión o redirigirlo a la página de registro.
Para ello, vamos a depender de las cookies de sesión. Hay un gran módulo que podemos utilizar para gestionar las cookies y las sesiones del sistema de ficheros en Go: el módulo módulo de sesiones Gorilla.
Para instalarlo, ejecute:
Añádelo a tu lista de importaciones en main.go y crea la cookie de sesión. Ya que estás ahí, declara también una struct llamado UserData que almacenará el nombre y el número de teléfono de un usuario registrado. Pasaremos esto struct a nuestra plantilla de página de inicio para que podamos mostrar esos detalles una vez que el usuario se haya registrado correctamente:
package main
import (
"html/template"
"log"
"net/http"
"os"
"github.com/gorilla/sessions"
"github.com/joho/godotenv"
"github.com/vonage/vonage-go-sdk"
)
var (
// Key must be 16, 24 or 32 bytes long (AES-128, AES-192 or AES-256)
key = []byte("super-secret-key")
store = sessions.NewCookieStore(key)
)
// UserData - Info from session that we'll use in the UI
type UserData struct {
Name string
Phone string
}
... Definir las rutas
Ahora debemos considerar qué rutas debemos definir para gestionar nuestro flujo de trabajo. Vamos a crear las siguientes rutas:
El punto de entrada de nuestra aplicación (
/). Esto mostrará la página de inicio (si el usuario está registrado), o redirigirá a la ruta/register(si no lo está).En registro (
/register). Mostrará el formulario de registro que, una vez enviado, iniciará el proceso de verificación llamando a la ruta/verifyruta.El sitio verificación (
/verify). Realiza una solicitud de Verify a la Verify API para enviar un código al dispositivo móvil del usuario. A continuación, redirige a la ruta/enter-coderuta.El introducir código (
/enter-code) presenta al usuario un formulario con un cuadro de texto en el que puede introducir el código PIN que ha recibido. Cuando envían el formulario, redirigimos a la ruta/check-coderuta.El código de control ruta (
check-code). Compara el código introducido con el enviado por la Verify API. Si coincide, almacenamos los datos del usuario en la sesión y redirigimos a la página de inicio (/). Si no hay coincidencia, registraremos un error en la consola. (Podríamos manejar esto con más elegancia, pero lo dejaré como ejercicio para el lector).En borrar ruta (
/clear). Esto borra la cookie de sesión para que pueda "dar de baja" a un usuario registrado. Útil para hacer pruebas.
¡Uf! Es bastante código para escribir. Afortunadamente, todo es bastante sencillo.
En primer lugar, defina algunos manejadores para las rutas en su función main e inicie su servidor a la escucha de peticiones entrantes:
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
os.Exit(1)
}
apiKey := os.Getenv("VONAGE_API_KEY")
apiSecret := os.Getenv("VONAGE_API_SECRET")
auth := vonage.CreateAuthFromKeySecret(apiKey, apiSecret)
verifyClient = vonage.NewVerifyClient(auth)
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
mux.HandleFunc("/", home)
mux.HandleFunc("/register", register)
mux.HandleFunc("/verify", verify)
mux.HandleFunc("/enter-code", enterCode)
mux.HandleFunc("/check-code", checkCode)
mux.HandleFunc("/clear", unregister)
log.Println("Starting server on :5000")
err = http.ListenAndServe(":5000", mux)
log.Fatal(err)
} El gestor de rutas de origen
Cree el home encima de la función main función. Este manejador se dispara cada vez que un usuario visita la raíz de su aplicación web en /.
Carga la cookie de sesión y comprueba si el registered valor de sesión. Si lo está, almacena la información de la cookie en una instancia de UserData y la utiliza para mostrar la página de inicio con el nombre y el número de teléfono del usuario.
Si no, redirige a la ruta /register ruta:
func home(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "acmeinc-cookie")
// Check if user is authenticated
if auth, ok := session.Values["registered"].(bool); !ok || !auth {
// Not authenticated, so user must register
http.Redirect(w, r, "/register", 302)
}
userData := UserData{
Name: fmt.Sprintf("%v", session.Values["name"]),
Phone: fmt.Sprintf("%v", session.Values["phoneNumber"]),
}
files := []string{
"./tmpl/home.page.gohtml",
"./tmpl/base.layout.gohtml",
}
/* Use the template.ParseFiles() function to read the files and store the
templates in a template set.*/
ts, err := template.ParseFiles(files...)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
return
}
err = ts.Execute(w, userData)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
}
} El gestor de rutas de registro
La ruta de registro muestra un formulario para que el usuario introduzca su nombre y número de teléfono para darse de alta en el servicio.
En primer lugar, añada fmt a la lista de importaciones. A continuación, cree el register encima de la función main función:
func register(w http.ResponseWriter, r *http.Request) {
files := []string{
"./tmpl/register.page.gohtml",
"./tmpl/base.layout.gohtml",
}
ts, err := template.ParseFiles(files...)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
return
}
err = ts.Execute(w, nil)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
}
} El gestor de rutas Verify
Cuando el usuario ha rellenado sus datos de registro y ha pulsado el botón "Registrarse", la aplicación llama a la /verify ruta. Es aquí donde creamos la petición inicial a la Verify API para generar un código y enviarlo al teléfono del usuario.
Cuando se envía una solicitud a la Verify API, ésta devuelve un icono request_id para identificar esa solicitud concreta. Necesitarás este ID cuando compruebes el código que introduce el usuario, así que guárdalo en una variable global:
package main
import (
...
)
var (
...
)
// UserData: Info from session that we'll use in the UI
type UserData struct {
...
}
var verifyClient *vonage.VerifyClient
var requestID stringIniciar esta solicitud es muy sencillo utilizando el SDK de Go. Llamamos al método VerifyClient.Request introduciendo el número de teléfono del usuario, el texto que queremos que aparezca en el SMS de verificación y algunas opciones para personalizar el proceso de verificación.
En este ejemplo, las opciones que estamos especificando son:
CodeLength: Esta es la longitud del código que enviaremos al usuario. Por defecto es de cuatro dígitos, pero estamos generando un código de seis dígitos.Lg: Especifica el idioma en el que vamos a enviar el SMS de verificación, de la lista de idiomas disponibles.WorkflowID: Verify API realiza varios intentos de enviar un código de verificación. Cómo y cuándo se realizan esos intentos depende del flujo de trabajo elegido. Estamos utilizando el flujo de trabajo con un ID de4que envía el código por SMS y espera dos minutos a que se verifique. Si no se verifica después de ese tiempo, envía el código de nuevo y espera otros tres minutos antes de cancelar el proceso. Otros flujos de trabajo utilizan una combinación de SMS y llamadas de voz con conversión de texto a voz para enviar el código de verificación.
También vamos a almacenar la información que nuestro usuario introdujo en nuestra cookie de sesión.
Escriba el código del verify manejador de ruta por encima de su main función:
func verify(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "acmeinc-cookie")
// retrieve user's name and phone number from the submitted form
userName := r.URL.Query().Get("name")
phoneNumber := r.URL.Query().Get("phone_number")
session.Values["name"] = userName
session.Values["phoneNumber"] = phoneNumber
session.Save(r, w)
log.Println("Verifying...." + userName + " at " + phoneNumber)
response, errResp, err := verifyClient.Request(phoneNumber, "GoTest", vonage.VerifyOpts{CodeLength: 6, Lg: "en-gb", WorkflowID: 4})
if err != nil {
fmt.Printf("%#v\n", err)
} else if response.Status != "0" {
fmt.Println("Error status " + errResp.Status + ": " + errResp.ErrorText)
} else {
requestID = response.RequestId
fmt.Println("Request started: " + response.RequestId)
// redirect to "check" page
http.Redirect(w, r, "/enter-code", 302)
}
} El gestor de rutas de introducción de código
Si todo ha ido según lo previsto, el usuario recibirá un SMS con un código PIN. Nuestra ruta /enter-code ruta proporcionará un formulario para que el usuario introduzca ese código.
Añada el enterCode a su aplicación:
func enterCode(w http.ResponseWriter, r *http.Request) {
files := []string{
"./tmpl/entercode.page.gohtml",
"./tmpl/base.layout.gohtml",
}
ts, err := template.ParseFiles(files...)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
return
}
err = ts.Execute(w, nil)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
}
} El gestor de rutas de código de control
Cuando el usuario ha introducido el código que recibió por SMS, tenemos que hacer una petición de verificación Verify para asegurarnos de que el código que ha introducido es el mismo que le enviaron. Para ello utilizamos el método VerifyClient.Check para ello, pasando el ID de la petición que hicimos en el verify y el código que han introducido en nuestra página.
Si el código coincide, establecemos el valor de registered cookie de sesión a true y redirigimos a la página de inicio. Si no, registraremos un mensaje de error en la consola:
func checkCode(w http.ResponseWriter, r *http.Request) {
// Retrieve the PIN code that the user entered
session, _ := store.Get(r, "acmeinc-cookie")
pinCode := r.URL.Query().Get("pin_code")
response, errResp, err := verifyClient.Check(requestID, pinCode)
if err != nil {
fmt.Printf("%#v\n", err)
} else if response.Status != "0" {
fmt.Println("Error status " + errResp.Status + ": " + errResp.ErrorText)
} else {
fmt.Println("Request complete: " + response.RequestId)
// Set user as authenticated and return to home page
session.Values["registered"] = true
session.Save(r, w)
http.Redirect(w, r, "/", 302)
}
} El gestor de rutas claro
Esto será útil para las pruebas. Te permite reiniciar el usuario haciendo una petición a http://localhost:5000/clearque borra la cookie de sesión.
Cree el unregister manejador de ruta como sigue:
func unregister(w http.ResponseWriter, r *http.Request) {
// Delete the session
session, _ := store.Get(r, "acmeinc-cookie")
session.Options.MaxAge = -1
session.Save(r, w)
http.Redirect(w, r, "/", 302)
} Pruébelo.
¡Ya ha terminado! Ahora es el momento de probar tu aplicación.
Inicie la aplicación ejecutando
go run main.goVisite http://localhost:5000 en su navegador
Introduzca su nombre y número de teléfono, incluyendo el código de marcación pero omitiendo los ceros a la izquierda. Por ejemplo, el número de teléfono móvil del Reino Unido
07700900001debe introducirse como447700900001:
Registration pageRecibirás un SMS con un código de seis dígitos:
The verification code, sent by SMSIntroduce ese código en la aplicación:
Entering the verificaiton codeSe le registra y se le envía a la página de inicio:
Acceder a la página de inicio]("Acceder a la página de inicio")
¿Y ahora qué?
Esperamos que hayas visto lo fácil que es utilizar Verify API y Go SDK para habilitar la autenticación de dos factores en tus aplicaciones. De hecho, ¡todo el proceso de verificación sólo ha requerido unas pocas líneas de código! La mayor parte del trabajo consistió en crear y gestionar la interfaz de usuario.
Nota: Si no has conseguido seguir el tutorial, puedes encontrar el código fuente completo de este tutorial en GitHub.
¿Crees que esta demo podría mejorar? Estoy de acuerdo. Estas son las que se me ocurren:
La salida no tan airosa cuando el usuario introduce un código erróneo. En su lugar, podría redirigir al usuario a la página de introducción del código y simultáneamente comprobar el progreso de la solicitude incluso cancelar una solicitud en curso después de dos intentos fallidos.
Formato en el que los usuarios deben introducir sus números de teléfono. Las API de Vonage esperan que los números de teléfono estén en formato E.164pero no hay razón por la cual debas imponerlo a tus usuarios. Puedes hacer que ingresen su número local y país desde una lista desplegable y usar la Number Insight API de Vonage para convertirlo al formato correcto. Esto también proporcionaría una práctica comprobación de que el número ingresado por el usuario es legítimo.
Esperamos que esté tan entusiasmado como nosotros con nuestro nuevo Go SDK. Para saber más sobre él y sobre Verify API, consulta los siguientes recursos:
