Get The Latest Articles In Your Inbox.

Join the other 2000+ savvy node.js developers who get article updates. You will receive only high-quality articles about Node.js, Cloud Computing and Javascript front-end frameworks. Unsubscribe anytime.

/Node.js

馃洃 No necesitas pasaporte.js - Gu铆a para la autentificaci贸n de node.js 鉁岋笍

Introducci贸n

Aunque los servicios de autenticaci贸n de terceros como Google Firebase, AWS Cognito y Auth0 est谩n ganando popularidad, y las soluciones de biblioteca todo en uno como passport.js son el est谩ndar de la industria, es com煤n ver que los desarrolladores nunca entienden realmente todas las partes involucradas en el flujo de autenticaci贸n.

Esta serie de art铆culos sobre la autenticaci贸n node.js, tiene por objeto desmitificar conceptos como el JSON Web Token (JWT), inicio de sesi贸n social (OAuth2), suplantaci贸n de identidad del usuario (un administrador puede iniciar sesi贸n como un usuario espec铆fico sin contrase帽a), trampas de seguridad comunes y los vectores de ataque.

Tambi茅n hay un repositorio GitHub con un flujo de autenticaci贸n completo de node.js que puedes usar como base para tus proyectos.

Tabla de contenidos

Requerimientos del proyecto 鉁嶏笍

Los requerimientos para este proyecto son:

  • Una base de datos para almacenar el correo electr贸nico y la contrase帽a del usuario, o el clientId y el clientSecret, o cualquier par de claves p煤blicas y privadas.
  • Un algoritmo criptogr谩fico fuerte y eficiente para encriptar las contrase帽as.

En el momento de escribir este art铆culo, considero que Argon2 es el mejor algoritmo criptogr谩fico que existe, por favor no use un simple algoritmo criptogr谩fico como SHA256, SHA512 o MD5.

Por favor, consulte este impresionante post para obtener m谩s detalles sobre Elijiendo un algoritmo de hasheo para la contrase帽a

C贸mo crear un registro 馃

Cuando se crea un usuario, la contrase帽a tiene que ser hasheada y almacenada en la base de datos junto con el correo electr贸nico y otros detalles personalizados (perfil de usuario, marca de tiempo, etc.)

Nota: Lea sobre la estructura del proyecto node.js en el art铆culo anteriorArquitectura del proyecto node.js a prueba de balas 馃洝锔**_

import * as argon2 from 'argon2';

class AuthService {
  public async SignUp(email, password, name): Promise<any> {
    const passwordHashed = await argon2.hash(password);

    const userRecord = await UserModel.create({
      password: passwordHashed,
      email,
      name,
    });
    return {
      // ASEG脷RATE DE NO DEVOLVER NUNCA LA CONTRASE脩A!!!!
      user: {
        email: userRecord.email,
        name: userRecord.name,
      },
    }
  }
}

El registro de usuario se ve as铆:

Registro de usuario - Base de datos MongoDB Robo3T para MongoDB

C贸mo crear un inicio de seci贸n 馃

diagrama de inicio de sesi贸n

Cuando el usuario realiza un inicio de seci贸n, esto es lo que sucede:

  • El cliente env铆a un par de Identificaciones P煤blicas y una Clave Privada, normalmente un correo electr贸nico y una contrase帽a
  • El servidor busca al usuario en la base de datos utilizando el correo electr贸nico.
  • Si el usuario existe en la base de datos, el servidor hashea la contrase帽a enviada y la compara con la contrase帽a hasheada que est谩 almacenada
  • Si la contrase帽a es v谩lida, emite un JSON Web Token (o JWT)

Esta es la clave temporal que el cliente tiene que enviar en cada solicitud a un endpoint autenticado

import * as argon2 from 'argon2';

class AuthService {
  public async Login(email, password): Promise<any> {
    const userRecord = await UserModel.findOne({ email });
    if (!userRecord) {
      throw new Error('User not found')
    } else {
      const correctPassword = await argon2.verify(userRecord.password, password);
      if (!correctPassword) {
        throw new Error('Incorrect password')
      }
    }

    return {
      user: {
        email: userRecord.email,
        name: userRecord.name,
      },
      token: this.generateJWT(userRecord),
    }
  }
}

La verificaci贸n de la contrase帽a se realiza utilizando la biblioteca de argon2 para prevenir 鈥渁taques basados en el tiempo鈥, que significa que cuando un atacante intenta forzar una contrase帽a basada en el s贸lido principio de cu谩nto tiempo tarda el servidor en responder.

En la siguiente secci贸n, discutiremos c贸mo generar un JWT

馃枑锔 Need a hand with your node.js application?

Messy code, scalability problems, security issues, feature planning, and architectural advice is just a couple of things that I can help you with.

Pero, 驴qu茅 es un JWT de todos modos? 馃懇馃彨

Un JSON Web Token o JWT es un objeto JSON codificado, en un string o Token.

Puedes pensar que es un reemplazo de una cookie, con varias ventajas.

El Token tiene 3 partes y se ve as铆:

Ejemplo de JSON Web Token

Los datos del JWT pueden ser decodificados en el lado del cliente sin el Secreto o Firma.

Esto puede ser 煤til para transportar informaci贸n o metadatos, codificados dentro del token, para ser usados en el frontend de la aplicaci贸n, como cosas como el rol de usuario, el perfil, la caducidad del token, etc.

Ejemplo de decodificaci贸n de JSON Web Token

C贸mo generar JWT en node.js 馃彮

Vamos a implementar la funci贸n generarToken necesaria para completar nuestro servicio de autenticaci贸n

Usando la biblioteca jsonwebtoken, que puedes encontrar en npmjs.com, somos capaces de generar un JWT.

import * as jwt from 'jsonwebtoken'
class AuthService {
  private generateToken(user) {

    const data =  {
      _id: user._id,
      name: user.name,
      email: user.email
    };
    const signature = 'MySuP3R_z3kr3t';
    const expiration = '6h';

    return jwt.sign({ data, }, signature, { expiresIn: expiration });
  }
}

Lo importante aqu铆 son los datos codificados, nunca se debe enviar informaci贸n sensible sobre el usuario.

La firma es el 鈥渟ecreto鈥 que se utiliza para generar el JWT, y es muy importante para mantener esta firma segura.

Si se ve comprometida, un atacante podr铆a generar tokens en nombre de los usuarios y robar sus sesiones.

Asegurando los endpoints y verificando el JWT 鈿旓笍

El c贸digo del frontend es ahora requerido para enviar el JWT en cada solicitud a endpoint seguro.

Una buena pr谩ctica es incluir el JWT en un encabezado, com煤nmente el encabezado de autorizaci贸n.

Encabezado de autorizaci贸n

Ahora en el backend, un middleware para las rutas express tiene que ser creado.

Middleware 鈥渋sAuth鈥

import * as jwt from 'express-jwt';

// Asumimos que el JWT vendr谩 en el encabezado Autorizaci贸n pero podr铆a venir en el req.body de la solicitud o en un query param, tienes que decidir qu茅 es lo mejor para ti.
const getTokenFromHeader = (req) => {
  if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
    return req.headers.authorization.split(' ')[1];
  }
}

export default jwt({
  secret: 'MySuP3R_z3kr3t', // Tiene que ser el mismo que usamos para firmar el JWT

  userProperty: 'token', // aqu铆 es donde el pr贸ximo middleware puede encontrar los datos codificados generados en los servicios/auth:generateToken -> 'req.token'

  getToken: getTokenFromHeader, // Una funci贸n para obtener el token de la solicitud
})

Es muy 煤til tener un middleware para obtener el registro completo del usuario actual, de la base de datos, y adjuntarlo a la solicitud.

export default (req, res, next) => {
 const decodedTokenData = req.tokenData;
 const userRecord = await UserModel.findOne({ _id: decodedTokenData._id })

  req.currentUser = userRecord;

 if(!userRecord) {
   return res.status(401).end('User not found')
 } else {
   return next();
 }
}

Ahora las rutas pueden acceder al usuario actual que est谩 realizando la solicitud.

  import isAuth from '../middlewares/isAuth';
  import attachCurrentUser from '../middlewares/attachCurrentUser';
  import ItemsModel from '../models/items';

  export default (app) => {
    app.get('/inventory/personal-items', isAuth, attachCurrentUser, (req, res) => {
      const user = req.currentUser;

      const userItems = await ItemsModel.find({ owner: user._id });

      return res.json(userItems).status(200);
    })
  }

La ruta 鈥渋nventory/personal-items鈥 est谩 ahora asegurada, es necesario tener un JWT v谩lido para acceder a ella, pero tambi茅n utilizar谩 el usuario actual de ese JWT para buscar en la base de datos los items correspondientes.

驴Por qu茅 un JWT es seguro?

Una pregunta com煤n que puedes tener despu茅s de leer esto es:

***Si los datos del JWT pueden ser decodificados en el lado del cliente, 驴puede un JWT ser manipulado de manera que cambie el ID de usuario u otros datos?

Mientras que puedes decodificar un JWT f谩cilmente, no puedes codificarlo con nuevos datos sin tener el 鈥淪ecreto鈥 que se us贸 cuando se firm贸 el JWT.

Esto es el 鈥減or que鈥 es tan importante no revelar nunca el secreto.

Nuestro servidor est谩 comprobando la firma en el middleware IsAuth la librer铆a express-jwt se encarga de eso.

Ahora que entendemos c贸mo funciona un JWT, pasemos a una funci贸n avanzada interesante.

馃憠 GET MORE ADVANCED node.js DEVELOPMENT ARTICLES

Join the other 2,000+ savvy node.js developers who get article updates.

No Spam馃. Unsubscribe anytime.

C贸mo hacerse pasar por un usuario 馃暤锔

La suplantaci贸n de identidad del usuario es una t茅cnica que se utiliza para iniciar sesi贸n como un usuario espec铆fico, sin conocer la contrase帽a del usuario.

Es una caracter铆stica muy 煤til para que los superadministradores, desarrolladores o soporte t茅cnico puedan resolver o debuggear un problema del usuario que s贸lo es visible con su sesi贸n.

No es necesario tener la contrase帽a del usuario para utilizar la aplicaci贸n en su nombre, s贸lo generar un JWT con la firma correcta y los metadatos de usuario requeridos.

Vamos a crear un endpoint que puede generar un JWT para iniciar la sesi贸n como un usuario espec铆fico, este endpoint s贸lo podr谩 ser utilizado por un usuario super-administrador

Primero, necesitamos establecer un rol m谩s alto para el usuario super-administrador, hay muchas maneras de hacerlo, una simple es simplemente agregar una propiedad de 鈥渞ol鈥 en el registro de usuario en la base de datos.

rol super-administrador en el registro de la base de datos de usuarios

En segundo lugar, vamos a crear un nuevo middleware que compruebe el rol del usuario.

export default (requiredRole) => {
  return (req, res, next) => {
    if(req.currentUser.role === requiredRole) {
      return next();
    } else {
      return res.status(401).send('Action not allowed');
    }
  }
}

Ese middleware necesita ser colocado despu茅s de los middlewares 鈥渋sAuth鈥 and 鈥渁ttachCurrentUser鈥.

Tercero, endpoint que genera un JWT para que el usuario se haga pasar por 茅l.

  import isAuth from '../middlewares/isAuth';
  import attachCurrentUser from '../middlewares/attachCurrentUser';
  import roleRequired from '../middlwares/roleRequired';
  import UserModel from '../models/user';

  export default (app) => {
    app.post('/auth/signin-as-user', isAuth, attachCurrentUser, roleRequired('super-admin'), (req, res) => {
      const userEmail = req.body.email;

      const userRecord = await UserModel.findOne({ email: userEmail });

      if(!userRecord) {
        return res.status(404).send('User not found');
      }

      return res.json({
        user: {
          email: userRecord.email,
          name: userRecord.name
        },
        jwt: this.generateToken(userRecord)
      })
      .status(200);
    })
  }

Por lo tanto, no hay magia negra aqu铆, el s煤per-admin conoce el correo electr贸nico del usuario por el que se quiere hacer pasar, y la l贸gica es bastante similar a la del inicio de sesi贸n, pero no se comprueba si la contrase帽a es correcta.

Eso es porque la contrase帽a no es necesaria, la seguridad del endpoint viene del middleware roleRequired.

Conclusi贸n 馃彈锔

Si bien es bueno confiar en los servicios y librerias de autenticaci贸n de terceros, para ahorrar tiempo de desarrollo, tambi茅n es necesario conocer la l贸gica y los principios subyacentes a la autenticaci贸n.

En este art铆culo exploramos las capacidades de JWT, por qu茅 es importante elegir un buen algoritmo criptogr谩fico para obtener las contrase帽as, y c贸mo suplantar a un usuario, algo que no es tan simple si se utiliza una libreria como passport.js.

En la siguiente parte de esta serie, vamos a explorar las diferentes opciones para proporcionar autenticaci贸n de 鈥淪ocial Login鈥 para nuestros clientes utilizando el protocolo OAuth2 y una alternativa m谩s f谩cil, un proveedor de autenticaci贸n de terceros como Firebase.

Vea el repositorio ejemplo aqu铆 馃敩

Recursos

Get The Latest Articles In Your Inbox.

Join the other 2000+ savvy node.js developers who get article updates.

You will receive only high-quality articles about Node.js, Cloud Computing and Javascript front-end frameworks.

Unsubscribe anytime.

santypk4

Sam Quinn - @santypk4

I help startup founders to build their dream software application by providing a team of expert software developers.

Read More
Latest Posts
Latest Open Source Projects
About us
Software on The Road LLC
134 N 4th St, Brooklyn, NY 11249, United States
contact@softwareontheroad.com
Software on The Road
coding the future