https://d226lax1qjow5r.cloudfront.net/blog/blogposts/building-a-check-in-app-with-nexmos-verify-api-dr/Build-a-Check-In-App_1200x675.jpg

Creación de una aplicación de registro con Verify API de Nexmo y Koa.js

Publicado el April 26, 2021

Tiempo de lectura: 13 minutos

Supongamos que está ejecutando un juego como The Amazing Raceen el que hay varios puntos de control que los jugadores tienen que alcanzar físicamente antes de poder completar el juego. Esta aplicación permite saber si un jugador ha alcanzado un punto de control o no.

Un poco sobre 2FA

Un flujo típico de autenticación de dos factores implica a una sola parte. Los usuarios introducen su número de teléfono móvil para recibir un SMS con el código de verificación y, a continuación, introducen dicho código en la interfaz de usuario para autenticar su identidad. Muy fácil.

Las cosas se ponen un poco más interesantes si quieres que esto sea cosa de dos. Un administrador de punto de control tendrá una lista de todos los jugadores y sus correspondientes números de teléfono. Cuando los jugadores lleguen al punto de control, el administrador activará un código de verificación que se enviará al teléfono del jugador.

A continuación, el jugador introducirá ese código de verificación a través de un formulario web en línea o responderá por SMS para confirmar su presencia en el puesto de control. En teoría, como el administrador no tiene forma de acceder al código de verificación, no podrá verificar a los jugadores que no estén presentes en el puesto de control.

Antes de que plantees la multitud de formas en que los jugadores pueden seguir confabulando con los administradores, permíteme asegurarte que soy consciente de ellas, pero esto es un MVP y volveremos a tratar la prevención del fraude (y una miríada de otras funciones) en futuras versiones. Tal vez.

Bibliotecas utilizadas

Nexmo Verify API de Nexmo se utiliza generalmente para la autenticación de dos factores, o para la autenticación sin contraseña. En lugar de escribir una funcionalidad de generación de OTP segura por mí mismo, elegí usar esto para mis códigos de verificación en su lugar.

Koa.js es el framework que hay detrás de la aplicación, para servir, enrutar, gestionar las peticiones y respuestas de la API, etc. Como el núcleo de Koa.js es bastante básico, hay que añadir varios middlewares cuando sea necesario.

Nunjucks es el motor de plantillas para la representación de datos en el frontend, mientras que lowdb es una base de datos JSON muy simple, ideal para prototipos como esta aplicación. Todas las funciones relacionadas con la base de datos se pueden cambiar fácilmente por otra base de datos más "seria".

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.

Una aplicación básica de Koa.js en Glitch

Si ya utilizas Glitchpor favor sáltate todo esto. Para las personas que aún no han descubierto la increíble plataforma que es Glitch, cuando aterrizas por primera vez, puedes elegir qué tipo de proyecto quieres construir. Hay 3 preajustes, un sitio web simple (sin backend), una aplicación Node y una aplicación Node con una base de datos SQlite. Para esta demo, puedes elegir la segunda opción.

Starting a new Node project on GlitchStarting a new Node project on Glitch

Para asegurarte de que tu proyecto persiste, es una buena idea registrarte en una Account de Glitch. Glitch ha estado haciendo mejoras en las características con bastante frecuencia, por lo que esto puede cambiar si usted está leyendo en el futuro, pero en el momento de la escritura, que apoyan el inicio de sesión a través de Facebook, GitHub, correo electrónico o código de inicio de sesión.

Sign in to a Glitch accountSign in to a Glitch account

Por defecto, las Applications Node en Glitch se ejecutan en Express, lo cual está totalmente bien. Este proyecto en particular utiliza Koa.js, así que hay un par de pasos más que seguir para eso.

Default package.json on a fresh Glitch Node projectDefault package.json on a fresh Glitch Node project

Al hacer clic en Herramientas, en la parte inferior izquierda de la pantalla, aparecerán algunas opciones, como Registros, Consola, Estadísticas del contenedor, etc.

Tools options on GlitchTools options on Glitch

Registros es ideal para tener abierto el desarrollo de su aplicación, porque todo lo que usted console.log() se muestra aquí.

Viewing logs on GlitchViewing logs on Glitch

Para personalizar los módulos npm que desea utilizar en su proyecto, puede acceder a la línea de comandos como lo haría en su máquina local o servidor remoto. Una cosa a tener en cuenta es que en lugar de npmGlitch utiliza pnpm como gestor de paquetes.

Accessing the Glitch consoleAccessing the Glitch console

Elimine express ejecutando lo siguiente:

pnpm uninstall express

A continuación, instala Koa.js ejecutando lo siguiente:

pnpm install koa --save

Para verificar los módulos npm que se utilizan en su proyecto, tendrá que actualizar el entorno:

refresh

Una vez hecho esto, deberías ver un indicador de "Error" junto a Herramientas. Eso está bien porque en el archivo server.js necesitamos el framework Express, que ya no existe.

Lo siguiente que hay que hacer es reescribir el código básico del servidor para utilizar Koa.js. Puedes hacerlo tú mismo o pegar el siguiente código en tu archivo recién creado.

const Koa = require('koa')
const port = process.env.PORT || 3000
const app = new Koa()

app.use(async ctx => {
  ctx.body = 'Hello Dinosaur 🦖'
})

const listener = app.listen(port, function() {
  console.log('Your app is listening on port ' + listener.address().port)
})

Si todo ha ido bien, al hacer clic en el botón Mostrar en la barra de navegación superior debe lanzar su aplicación en una nueva ventana con el texto, "Hola Dinosaurio 🦖".

Check that Koa.js is running fineCheck that Koa.js is running fine

Estructura de las Applications

Por derecho, debería haber alguna gestión de usuarios adecuada para los administradores. Si accedes a la demo en vivo en Glitch, verás que hay alguna apariencia de protección por contraseña, pero es una implementación muy ingenua para este simple prototipo. El prototipo simplemente presenta la idea de cómo funcionaría la interfaz.

Esto significa que la aplicación tendrá 3 páginas: la página de inicio de sesión, la página del administrador y la página de introducción del código de verificación.

Rough screen sketches for login page, administrator page and verification code entry pageRough screen sketches for login page, administrator page and verification code entry page

Como ya se ha mencionado, es necesario instalar algunos middlewares adicionales. Este proyecto utiliza los siguientes:

  • koa-static para servir activos estáticos

  • koa-bodyparser para gestionar los datos enviados a través de peticiones POST

  • koa-router para el enrutamiento

  • koa-views para renderizar plantillas nunjucks (también requiere que nunjucks esté instalado)

  • koa-session para protección básica con contraseña (no para producción)

Servir activos estáticos

const serve = require('koa-static')
app.use(serve('./public'))

Esta es probablemente la parte menos complicada de cubrir, el servicio de activos estáticos como CSS y Javascript del lado del cliente desde el directorio /público de la carpeta /public.

Enrutamiento y renderizado básicos

El plan es conseguir que todo funcione sin ningún tipo de Javascript del lado del cliente. Así que las entradas de usuario se envían a través de formularios HTML, y si es necesario, una redirección a la página correspondiente después de la presentación. Cada página es su propio archivo HTML y se renderiza con koa-viewsque proporciona una función render() función.

const Router = require('koa-router')
const views = require('koa-views')
const router = new Router()

app.use(views('./views', { map: { html: 'nunjucks' }}))

router.get('/login', (ctx, next) => {
  return ctx.render('./login')
})

router.get('/', (ctx, next) => {
  return ctx.render('./index')
})

router.get('/verify/:phone', (ctx, next) => {
  const phone = ctx.params.phone
  return ctx.render('./verify')
})

router.get('/result/:phone', (ctx, next) => {
  const phone = ctx.params.phone
  return ctx.render('./result')
})

koa-router también proporciona una forma de acceder a los parámetros de URL a través de ctx.paramsutilizado para */verify/* y /result/**, que se utiliza para hacer coincidir los números de teléfono con el código de verificación generado. Esto tendrá más sentido cuando veamos cómo funciona Verify API.

Verify API

Verify API consta de dos pasos. El primero es enviar un SMS con la contraseña de un solo uso (OTP) al número de teléfono del destinatario.

nexmo.verify.request({
  number: RECIPIENT_NUMBER,
  brand: NEXMO_BRAND_NAME
}, (err, result) => {
  if (err) {
    console.error(err)
  } else {
    const verifyRequestId = result.request_id
    console.log('request_id', verifyRequestId)
  }
})

La respuesta HTTP de la solicitud Verify API tiene el siguiente aspecto:

{
  "request_id": "aaaaaaaa-bbbb-...",
  "status": "0",
  "error_text": "error"
}

Si el status es distinto de 0la solicitud no ha tenido éxito y los detalles sobre por qué se pueden encontrar en error_text. En caso contrario, el destinatario recibirá una contraseña por SMS. A continuación, debe introducir esta OTP en su aplicación, que puede verificarse con la OTP original. request_id.

nexmo.verify.check({
  request_id: REQUEST_ID,
  code: CODE
}, (err, result) => {
  if (err) {
    console.error(err)
  } else {
    console.log(result)
  }
})

La respuesta HTTP de la Verify API tiene el siguiente aspecto:

{
  "request_id": "aaaaaaaa-bbbb-...",
  "event_id": "0A00000012345678",
  "status": "0",
  "price": "0.10000000",
  "currency": "EUR",
  "error_text": "error"
}

Manejo de varios jugadores

El administrador del punto de control necesita hacer un seguimiento de todos los jugadores, por lo que puede ser una buena idea utilizar una base de datos para almacenar esa información. Esta demo utiliza lowdbque es una pequeña base de datos local JSON basada en Lodash, pero puedes usar la base de datos que quieras.

Hay una serie de funciones relacionadas con la base de datos para añadir jugadores, recuperar información sobre los jugadores y actualizar la información de los jugadores. Al organizar las funciones de este modo, resulta más fácil cambiar de base de datos, ya que la lógica sigue siendo la misma.

function dbAddPlayer(data) {
  db.get('players')
    .push({ name: data.name, phone: data.phone })
    .write()
  console.log('New user inserted in the database')
}

function dbGetPlayers() {
  return db.get('players').value()
}

function dbPlayerCount() {
  return db.get('players').size().value()
}

function dbAddId(phone, requestId, mode, status) {
  db.get('players')
    .find({ phone: phone })
    .assign({ id: requestId, delivery: mode, status: status })
    .write()
}

function dbUpdateStatus(requestId, status) {
  db.get('players')
    .find({ id: requestId })
    .assign({ status: status })
    .write()
}

function dbFindPlayer(phone) {
  return db.get('players').find({ phone: phone }).value()
}

function dbClear() {
  db.get('players')
    .remove()
    .write()
  console.log('Database cleared')
}

Puede utilizar un formulario web para recoger la información necesaria para crear un nuevo jugador. En esta demostración sólo se requieren dos campos: el nombre del jugador y su número de teléfono.

<form id="addPlayerForm" action="add" method="post">
  <h2>Add player</h2>
  <div class="inputs">
    <label>
      <span>Name</span>
      <input name="name" required>
    </label>
    <label>
      <span>Phone</span>
      <input type="tel" name="phone" required>
    </label>
  </div>
  <button id="addPlayer">Add</button>
</form>

Al enviar este formulario se enviará una solicitud POST a /addlo que significa que necesitas crear una ruta para manejar estos datos entrantes, y luego almacenarlos en la base de datos.

router.post('/add', (ctx, next) => {
  const payload = ctx.request.body
  dbAddPlayer(payload)
  ctx.status = 200
  ctx.response.redirect('/')
})

A continuación, puede renderizar la información de la base de datos en la página con Nunjucks. Nunjucks proporciona una función render() que le permite pasar datos a su plantilla Nunjucks. Modifique la GET ruta para / para que pueda pasar la información del jugador de la base de datos a la página.

router.get('/', (ctx, next) => {
  const players = dbGetPlayers()
  return ctx.render('./index', { players: players })
})

A continuación, puedes introducir los valores de los jugadores en la plantilla y representarlos como desees estructurar los datos de los jugadores.

Displaying player data on the frontendDisplaying player data on the frontend

Activación de la OTP

En Verify API requiere el número de teléfono del jugador para activar la solicitud de OTP. Puede enviar esta información al servidor a través de otro formulario web, esta vez enviado a */verify/{{ player.phone }}*lo que le permitirá obtener el número de teléfono a través de ctx.params.phone y pasarlo a la función de solicitud de Verify API.

router.post('/verify/:phone', async (ctx, next) => {
  const phone = ctx.params.phone
  const result = await verify(phone)
  dbAddId(phone, result.request_id, payload.delivery, 'pending')
  ctx.status = 200
  ctx.response.redirect('/')
})

// Verify API's request function
async function verify(number) {
  return new Promise(function(resolve, reject) {
    nexmo.verify.request({
      number: number,
      brand: process.env.NEXMO_BRAND_NAME
    }, (err, result) => {
      if (err) {
        console.error(err)
        reject(err)
      } else {
        resolve(result)
      }
    })
  })
}

// Add request ID to the database
function dbAddId(phone, requestId, mode, status) {
  db.get('players')
    .find({ phone: phone })
    .assign({ id: requestId, delivery: mode, status: status })
    .write()
}

La dirección request_idque forma parte de la respuesta de la API, será necesaria para la siguiente función check() posterior. Esta función se utilizará para verificar el código request_id con el código introducido por el jugador. Esto se almacena en la base de datos mediante la función dbAddId() función.

Trigger the OTP for each phone numberTrigger the OTP for each phone number

Comprobación de la OTP

Los jugadores pueden introducir el código de verificación en la URL exclusiva, */verify/{{ player.phone }}*. Tendría que pasar el número de teléfono de nuevo a la página para que su GET ruta se parecería a la de /que incluye la función render() función.

router.get('/verify/:phone', (ctx, next) => {
  const phone = ctx.params.phone
  return ctx.render('./verify', { phone: phone })
})

El formulario web para que los jugadores introduzcan su OTP tendrá este aspecto:

<form method="post" action="/check" id="checkPinForm">
  <input name="pin" type="number">
  <input type="hidden" name="phone" value="{{ phone }}">
  <button>Submit</button>
</form>

Web form for entering the OTP sent to players' phonesWeb form for entering the OTP sent to players' phones

Cuando el jugador envía la OTP, puede recuperar el original request_id original de la base de datos y pasar tanto el request_id y el PIN a la función Verify API's check() de la API de verificación.

router.post('/check', async (ctx, next) => {
  const payload = await ctx.request.body
  const phone = payload.phone
  const code = payload.pin
  
  const requestId = dbFindPlayer(phone).id

  const result = await check(requestId, code)
  dbUpdateStatus(requestId, result.status)
  
  ctx.status = 200
  ctx.response.redirect('/result/' + payload.phone)
})

// Verify API's check function
async function check(requestId, code) {
  return new Promise(function(resolve, reject) {
    nexmo.verify.check({
      request_id: requestId,
      code: code
    }, (err, result) => {
      if (err) {
        console.error(err)
        reject(err)
      } else {
        resolve(result)
      }
    })
  })
}

// Update player status in the database
function dbUpdateStatus(requestId, status) {
  db.get('players')
    .find({ id: requestId })
    .assign({ status: status })
    .write()
}

Una verificación correcta devolverá un código de estado de 0que puede utilizar para determinar qué mensaje de estado mostrar al usuario en la página de resultados.

<main>{% raw %}
  {% if status == 0 %}
  <p>Code verified successfully.</p>
  <p>You can close this window now.</p>
  {% else %}
  <p>Something went wrong…</p>
  <p>Please contact the administrator for more information.</p>
  {% endif %}{% endraw %}
</main>

Verification results page displayed to the userVerification results page displayed to the user

Otras cosas que puede hacer

Nexmo Messages API también se puede utilizar para añadir funcionalidad adicional a este proyecto. Por ejemplo, podría activar un SMS adicional que contenga el enlace a la página de verificación para mejorar la experiencia del usuario.

O, tal vez su juego se desarrolla en un lugar bastante remoto, piense en Pulau Perhentian (foto de abajo), o por alguna razón tus jugadores no tienen datos móviles. Entonces, en lugar de comprobar el código a través de una interfaz web, los jugadores pueden responder por SMS.

 Pulau Perhentian Pulau Perhentian

La versión alojada en Glitch cubre los dos escenarios anteriores, así que no dudes en echarle un vistazo y remezclarla.

remix this

Este prototipo es una demostración de lo que es posible con Verify API, pero para que se convierta en una aplicación completa, hay que tener en cuenta muchas cosas. Una de las más importantes es la gestión de errores. Verify API devuelve un valor de estado de 0 para las consultas correctas, pero cualquier otro valor indica un error.

Estos errores deben tratarse y la interfaz de usuario del frontend debe reflejar cualquier error potencial que impida una verificación correcta. También podría ser una buena idea implementar algún tipo de validación en el frontend, o incluso utilizar la Number Insight API de Nexmo de Nexmo para garantizar que sólo se pasan números de teléfono válidos a la Verify API.

¿Y ahora qué?

Si quieres saber más sobre estas API, aquí tienes algunos enlaces que pueden resultarte útiles:

Compartir:

https://a.storyblok.com/f/270183/384x384/46621147f0/huijing.png
Hui Jing ChenAntiguos alumnos de Vonage

Hui Jing es defensora de los desarrolladores en Nexmo. Tiene un amor desmesurado por CSS y la tipografía, y en general es una apasionada de todo lo relacionado con la web.