En el vertiginoso mundo de la programación web, la seguridad es una prioridad ineludible. Ya sea que estés desarrollando una aplicación de comercio electrónico, una plataforma de redes sociales o cualquier otro tipo de sistema en Node.js, proteger los datos y las interacciones de tus usuarios es crucial. En este emocionante viaje a través de dos poderosas herramientas de seguridad, Bcrypt y JWT (JSON Web Tokens), te mostraremos cómo puedes fortalecer la seguridad de tus aplicaciones web de manera efectiva y elegante.
Bcrypt y JWT son dos tecnologías ampliamente utilizadas en el ecosistema de Node.js, y su combinación ofrece una sólida defensa contra una variedad de amenazas cibernéticas. Bcrypt se encarga de proteger las contraseñas almacenadas en tu base de datos, mientras que JWT te permite gestionar la autenticación y la autorización de manera segura y eficiente.
En este artículo, te guiaremos a través de los conceptos fundamentales de ambas tecnologías, explicando cómo funcionan y por qué son esenciales para la seguridad de tu aplicación. Aprenderás a implementar Bcrypt para almacenar contraseñas de forma segura, evitando vulnerabilidades comunes como la exposición de contraseñas en texto plano. Luego, exploraremos cómo JWT te permite crear tokens de acceso y garantizar que solo los usuarios autorizados puedan acceder a ciertas partes de tu aplicación.
Autenticación: En el tejido mismo de cualquier sistema de seguridad, la autenticación es el proceso que verifica la identidad de un usuario. Es como abrir la puerta principal de una casa solo para aquellos que poseen una llave válida. En el contexto de las aplicaciones web, esto implica la confirmación de que un usuario es quien dice ser. Comúnmente, esto se logra mediante la combinación de un nombre de usuario y una contraseña, pero también puede incluir otros métodos como la autenticación de dos factores (2FA) o la autenticación biométrica. La autenticación efectiva es el primer paso para garantizar que solo las personas autorizadas tengan acceso a los recursos de una aplicación.
Autorización: Una vez que hemos autenticado a un usuario y sabemos quiénes son, la autorización entra en juego. Esta etapa determina qué acciones o recursos específicos tienen permiso para acceder dentro de la aplicación. Siguiendo con la analogía de la puerta de la casa, la autorización decide qué habitaciones o áreas están disponibles para el usuario después de que se haya autenticado correctamente. En el contexto de las aplicaciones web, la autorización se basa en roles y permisos que definen quién puede realizar qué acciones. Un ejemplo común es el acceso de un usuario normal versus un administrador, donde el administrador tiene acceso a funciones adicionales y datos confidenciales.
Encriptación: Imagina que tus datos son cartas escritas en un idioma desconocido que solo tú y el destinatario pueden entender. La encriptación es esa técnica que convierte tus datos en algo incomprensible para cualquiera que intente interceptarlos, a menos que tengan la clave adecuada para descifrarlos. En el mundo de las aplicaciones web, la encriptación se aplica a menudo para proteger datos en tránsito (por ejemplo, cuando viajan a través de una conexión segura HTTPS) y datos en reposo (cuando se almacenan en bases de datos o sistemas de archivos). Sin encriptación, los datos podrían ser vulnerables a accesos no autorizados o ataques de interceptación.
Estos tres pilares, autenticación, autorización y encriptación, forman la base de una sólida estrategia de seguridad en aplicaciones web. Ahora que hemos establecido estos conceptos, estaremos mejor preparados para explorar cómo Bcrypt y JWT en Node.js contribuyen a la protección de nuestros sistemas y datos de manera efectiva y confiable. ¡Sigamos adelante en este viaje de conocimiento!
bcrypt
Bcrypt es una técnica de encriptación utilizada específicamente para almacenar contraseñas de manera segura en aplicaciones web y bases de datos. A diferencia de las funciones de hash estándar, como MD5 o SHA-1, Bcrypt está diseñado para ser lento y resistente a los ataques de fuerza bruta y diccionario. Uno de los conceptos clave en Bcrypt es el «salt» (sal), que contribuye en gran medida a su seguridad.
Salt (Sal): El salt es una cadena de caracteres aleatoria que se genera de forma única para cada contraseña antes de aplicar el algoritmo de hash Bcrypt. El propósito del salt es hacer que las contraseñas en la base de datos sean más resistentes a los ataques de fuerza bruta y ataques de tablas arcoíris (rainbow tables).
El salt se concatena con la contraseña del usuario antes de aplicar el algoritmo de hash Bcrypt. Debido a que cada usuario tiene un salt único, aunque dos usuarios tengan la misma contraseña, sus hashes finales serán diferentes debido al salt único. Esto significa que un atacante no puede usar tablas arcoíris precalculadas (que asumen que las contraseñas sonlas mismas) para descifrar contraseñas.
import bcrypt from 'bcrypt'
let miPassword = 'Dani123' //pass en texto plano
const salt = await bcrypt.genSaltSync(10);
const passwordHashed = await bcrypt.hash(miPassword, salt)
console.log(passwordHashed)
//Similar a: $2b$10$aKkpEPaCFyua.Yy4LunmweshtF6zOlQlRbmvXcleWnQDv4dEjpRPa
const compare1 = await bcrypt.compare('vsdfasdf', passwordHashed);
console.log(compare1) //false
const compare2 = await bcrypt.compare('Dani123', passwordHashed);
console.log(compare2) //true
Bcrypt es sumamente común para encriptar las contraseñas de los usuarios antes de insertar la contraseña de los usuarios de tu aplicación en la base de datos, luego, en el proceso de login utilizar el método compare para saber si la contraseña que te envían coincide con lo que has almacenado en tu base de datos
JWT
JSON Web Tokens (JWT) es un estándar abierto (RFC 7519) que se utiliza para transmitir información de manera segura entre dos partes, típicamente entre un servidor y un cliente, como parte de una autenticación o una solicitud de autorización. Los JWT se componen de tres partes: el header (encabezado), el payload (carga útil) y la signature (firma).
Header (Encabezado): El header de un JWT contiene información sobre cómo se debe procesar el token y qué algoritmo se utiliza para firmarlo. Es un objeto JSON que incluye dos partes principales: el tipo de token (generalmente «JWT») y el algoritmo de hashing utilizado, como «HS256» o «RS256».
{
"alg": "HS256",
"typ": "JWT"
}
Payload (Carga Útil): El payload lleva la información que se quiere transmitir. Puede contener claims (declaraciones) estándar como «sub» (sujeto), «exp» (tiempo de expiración), «iss» (emisor), y claims personalizados definidos por el emisor y el receptor. El payload es la parte del token que suele ser legible por humanos y puede transportar datos como el ID del usuario, roles y otros atributos.
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Signature (Firma): La signature es el resultado de tomar el header codificado, el payload codificado, una clave secreta (o clave pública/privada en el caso de JWTs RSA), y aplicar un algoritmo de hashing especificado en el header. Esta firma se utiliza para verificar la integridad del token y asegurarse de que no ha sido modificado por terceros no autorizados. Solo el servidor que posee la clave secreta (o privada) puede generar una firma válida.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
Ahora llevemos está explicación a la parte práctica de Node.js
Para iniciar instala la dependencia en tu proyecto
npm i jsonwebtoken
Luego utiliza las funciones sign y verify para encriptar y descriptar tu token
import jwt from 'jsonwebtoken'
const objetoQueQuieroEncriptar = {
id: "sdkljh23sdfawl23948",
admin: true,
nombre: "Dani",
apellido: "Sego"
}
const token = jwt.sign(
objetoQueQuieroEncriptar,
"miContrasenaSoloParaBack")
console.log(token)
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InNka2xqaDIzc2RmYXdsMjM5NDgiLCJhZG1pbiI6dHJ1ZSwibm9tYnJlIjoiRGFuaSIsImFwZWxsaWRvIjoiU2VnbyIsImlhdCI6MTY5NDU0NTEzOH0.GwVOArxQP-3Loa8WOrZLt_gKOXlntcfQELXUPEqVCQ8
jwt.verify(token, "miContrasenaSoloParaBack", (err, data) => {
if(err) return err
console.log(data)
/*
resultado:
{
id: 'sdkljh23sdfawl23948',
admin: true,
nombre: 'Dani',
apellido: 'Sego',
iat: 1694545138
}*/
})
Flujo de registro & Login
El flujo básico se refleja en el siguiente gráfico
Primero el usuario se registra enviando usuario y contraseña, el servidor se encarga en encriptar la contraseña antes de almacenarla en la base de datos.
Para realizar la acción de login el usuario envía su usuario y contraseña, el servidor recupera el usuario de la base de datos y compara la contraseña envíado en texto plano contra la contraseña almacenada en la base de datos encriptada para ver si coindicen.
Si el usuario y contraseña coinciden responde la solicitud con un token.
Por último, el cliente solicita algún recurso del servidor y envía el token a través del cual el servidor verificará si puede acceder o no al recurso solicitado.
