
Compartir:
Antiguo desarrollador .NET Advocate @Vonage, ingeniero de software poliglota full-stack, AI/ML
Validación de mensajes entrantes desde Messages API de Vonage
Tiempo de lectura: 7 minutos
Introducción a los Webhooks
El API de Messages de Vonage es fenomenal. Sé que como defensores no debemos tener favoritos, pero en realidad es mi favorita de las API de Vonage. Te permite enviar mensajes a mensajes por WhatsApp, Facebook Messenger, Viber, SMS y MMS muy fácilmente. Y en el otro extremo, te permite recibir mensajes a través de la mayoría de esos canales (los mensajes SMS se envían de vuelta a los puntos finales del nivel de cuenta).
Para que tu aplicación reciba un mensaje entrante (o el estado de un mensaje saliente) de Vonage, tu aplicación debe tener un punto final HTTP de acceso público al que Vonage pueda enviar el mensaje. Este mensaje se denomina webhook. Solemos ver muchas preguntas sobre la seguridad y los webhooks, así que voy a mostrarte cómo puedes proteger tus webhooks de mensajes. Vamos a lograr esto con una combinación de autenticación de portador y validación de carga útil.
Preocupaciones en torno a la seguridad de Webhook
La mayor pregunta que recibimos en torno a los webhooks, más allá de lo que son, es cómo proteger los webhooks para asegurarse de que los malos actores no están enviando webhooks maliciosos. ¿Qué puede hacer un atacante si consigue acceder a nuestros puntos finales de webhooks y tiene una masa crítica de datos de usuario para lanzarnos? Es una preocupación válida.
Piénsalo así: Alice es desarrolladora de aplicaciones y necesita recibir un mensaje de WhatsApp de Bob. Bob enviará un mensaje de WhatsApp a la aplicación de Alice. Cuando se reciba ese mensaje de WhatsApp, Vonage enviará un mensaje al punto final HTTP de Alice para notificar a su aplicación sobre el mensaje entrante. Ese punto final debe estar disponible públicamente. Si Chuck, un hacker malintencionado, encuentra el punto final webhook de Alice, puede hacerse pasar por Bob para los mensajes entrantes o falsificar los estados de los mensajes salientes.
Autenticar webhooks entrantes
Vonage utiliza autorización de portador de token web JSON (JWT) para los webhooks enviados desde Messages API para permitirte autenticar fácilmente el webhook. El token portador es un token HMAC-SHA256, lo que significa que verificar la validez del JWT es tan simple como decodificar el token con su secreto de firma. El secreto que debe utilizar es el mismo que se muestra en la página de configuración en el panel de control. Se recomienda que este secreto de firma sea de al menos 32 bits para dificultar un ataque de fuerza bruta. El secreto de firma de tu Account es un secreto compartido entre tú y Vonage.
signing secret
Validar la carga útil de un webhook entrante
Además de autorizar el token, es una buena idea comprobar que la carga útil del webhook coincide con lo que el token dice que debe ser. Una vez descodificados, los JWT tienen su propia carga útil JSON, cuyos campos se denominan "claims". Para evitar que un atacante robe uno de tus tokens y lo reproduzca, puedes utilizar una de estas reclamaciones, el campo payload_hash. El payload_hash es un hash SHA-256 de la carga útil del webhook. Simplemente ejecutando la carga útil del mensaje entrante a través de un hash SHA-256 y comparándolo con la afirmación payload_hash del webhook, puedes estar seguro de que el token que estás recibiendo no es una repetición.
Comprobar la hora de generación de un token
Otro dato importante es iat-Esto significa "emitido en", y es la marca de tiempo UTC Unix de cuando se generó el token. Puede comparar iat con la marca de tiempo UTC Unix actual para comprobar la antigüedad de la marca de tiempo y así protegerse de los tokens potencialmente obsoletos.
Traducir a código
Tomemos estos Concepts y convirtámoslos en código. Te voy a mostrar cómo hacer esto en Node.js, pero estas técnicas están disponibles en prácticamente todos los lenguajes de programación.
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.
Recopilar dependencias
Cree un nuevo directorio llamado signed_webhooks y cd en él. En este directorio, ejecute npm install dotenv jsonwebtoken js-sha256 express body-parser.
Crear archivos y añadir configuración
En nuestro signed_webhooks vamos a crear un directorio server.js y un archivo .env archivo. El archivo server.js es donde irá nuestro código de servidor y nuestro archivo .env es donde nuestra configuración va a vivir. En el archivo .env añada un solo campo, NEXMO_API_SIGNATURE_SECRETy establecer que el secreto de la firma de su configuración del panel del panel de control.
Inicializar relaciones
Ahora que hemos reunido todas nuestras dependencias y configurado nuestro servidor, necesitamos añadir el código del servidor. Empecemos por inicializar nuestras dependencias. Añade lo siguiente a server.js:
require('dotenv').config();
const jwt = require("jsonwebtoken");
const sha256 = require('js-sha256');
const app = require('express')();
const bodyParser = require('body-parser');
const NEXMO_API_SIGNATURE_SECRET = process.env.NEXMO_API_SIGNATURE_SECRET;
if(!NEXMO_API_SIGNATURE_SECRET){
throw "Missing Signature Secret";
}Este código traerá en todas nuestras dependencias y tirar en el secreto de la firma de nuestro entorno.
Añadir ruta de mensajes entrantes
A continuación, tenemos que configurar la ruta para inbound-message y status. Asumiremos que los webhooks que vamos a recibir van a ser peticiones POST por lo que añadiremos una ruta a /webhooks/inbound-message y /webhooks/status y configuraremos POST para que sean enrutadas a través de nuestra función handleWebhook función.
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: true
}))
app
.route('/webhooks/inbound-message')
.post(handleWebhook);
app
.route('webhooks/status')
.post(handleWebhook) Gestión de mensajes entrantes
Cuando recibamos un mensaje entrante, extraeremos el cuerpo y lo añadiremos a la carga útil. A continuación, dividiremos la cabecera de autorización en espacios en blanco (la cabecera de autorización tendrá la forma "Bearer Token", por lo que al dividir en espacios en blanco y tomar la parte del token obtendremos el JWT).
Con el token recuperado, podemos decodificar el JWT utilizando nuestro secreto de firma. Como hemos comentado antes, esta acción de descodificación equivale a validar la autenticidad del token. Si el JWT no es válido o la cabecera auth está malformada, la operación de decodificación lanzará una excepción y devolverá un 401. Si somos capaces de decodificar el token, habremos verificado la autenticidad del token - por lo tanto, si estás detrás de TLS y no te importa validar la carga útil, puedes devolver con seguridad un 200 en este punto. Si, por el contrario, decide verificar la carga útil, es tan sencillo como ejecutar un comando JSON.stringify sobre el payload y compararlo con el payload_hash del JWT decodificado. Si detectas manipulación, puedes devolver un 401 desde el endpoint para decirle a la gente que no están autorizados. Por último, vamos a decirle a nuestra aplicación que escuche en un puerto especificado o en el puerto 3000.
Todo esto se consigue en la siguiente handleWebhook solicitud:
function handleWebhook(request, response){
const payload = Object.assign(request.query, request.body)
try{
let token = request.headers.authorization.split(" ")[1]
var decoded = jwt.verify(token, NEXMO_API_SIGNATURE_SECRET, {algorithms:['HS256']});
if(sha256(JSON.stringify(payload))!=decoded["payload_hash"]){
console.log("tampering detected");
response.status(401).send();
}
else{
console.log("Success");
response.status(204).send();
}
}
catch(err){
console.log('Bad token detected')
response.status(401).send()
}
}
app.listen(process.env.PORT || 3000) Pruebas
Para las pruebas, sólo vamos a ejecutar localmente. Arrancar el servidor es sencillo: ejecute node server.jsy el servidor arrancará.
Configurar ngrok
Para enrutar webhooks a nuestro servidor, vamos a utilizar ngrok. Ngrok nos permite crear un túnel para nuestro servidor local. Ejecute el siguiente comando.
Esto abrirá una sesión ngrok que tendrá el siguiente aspecto http://random.ngrok.io-swap out random con cualquiera que sea el hash aleatorio al principio de la URL, a continuación, agregue la ruta a /webhooks/inbound-message y tendrás la URL para tus webhooks.
ngrok
Configurar Webhooks
Ahora que tenemos un túnel a nuestro servidor, lo último que tenemos que hacer antes de recibir mensajes es configurar nuestros webhooks. Para realizar pruebas, es posible que desee utilizar la caja de arena de Messages API (puede encontrar instrucciones en Martyn's blog post sobre el tema o en los Mensajes API Sandbox docs.
Para la producción, tendrá que configurar los webhooks de su aplicación. Puede hacerlo en ${CUSTOMER_DASHBOARD_URL}/applications/:appid/editsustituyendo :appid por el id de tu aplicación. También puedes configurarlos con la CLI de Nexmo o la API de Aplicaciones.
Después de editarla, la configuración del webhook de tu aplicación para los mensajes debería tener este aspecto:
application webhooks
Ahora que todo está configurado, podemos enviar un mensaje de prueba a tu número de WhatsApp, Viber o Facebook Messenger, y tu servidor validará los webhooks entrantes.
Validación de SMS entrantes
La validación de SMS entrantes queda fuera del ámbito de este artículo, pero disponemos de una metodología similar para validar los mensajes SMS entrantes. Puedes consultar cómo hacerlo en nuestra Documentación para desarrolladores.
Recursos
El código de este post está disponible en GitHub.
Encontrará una explicación más detallada sobre el funcionamiento de la autenticación JWT en nuestros documentación para desarrolladores.
Si tienes un JWT que quieres decodificar manualmente, puedes hacerlo fácilmente con decodificador de jwt.io.
