You don't have access to this class

Keep learning! Join and start boosting your career

Aprovecha el precio especial y haz tu profesión a prueba de IA

Antes: $249

Currency
$209
Suscríbete

Termina en:

2 Días
5 Hrs
13 Min
29 Seg

Implementando hashing para usuarios

6/20
Resources

How is password hashing implemented on user and client endpoints?

When working with sensitive data such as passwords, it is imperative to ensure that they are stored securely. In this content, we will explore how password hashing can be integrated into an API to improve security when creating both users and clients.

What steps should be taken to hash passwords when creating a user?

First, it is vital that passwords are not stored in plain text within our database. Using a library such as Bcrypt to encrypt passwords is a common practice. The implementation requires the following steps:

  1. Import the Bcrypt library: This is accomplished by adding the corresponding import line in the user service file.

  2. Generate the hash: Before storing the password in the database, a hash of the original password is generated using a method provided by Bcrypt. Here is an example in JavaScript:

    const bcrypt = require('bcrypt');const saltRounds = 10;
    function hashPassword(password) { return bcrypt.hash(password, saltRounds);}
  3. Replace password with hash in the user object: Once the hash is generated, the original password is replaced with the hash before storing the user in the database:

    const newUser = {. ..userData, password: hashedPassword,};
  4. Verify that the password is not returned: Make sure not to return the password (not even the hash) to the client once the user is created. Use delete to remove the password field:

    delete newUser.password;

How is the hash applied at the client creation endpoint?

Encryption logic is also applied when clients are being created. These steps ensure that related passwords continue to be hashed, keeping the data secure:

  1. Clone and modify the data object: When creating a client, clone the contained object and apply the hash to the corresponding sub-object:

    const cloneData = { ...data };cloneData.user.password = await hashPassword(cloneData.user.password);
  2. Delete the password field in the response: Apply the same deletion strategy so that the password is not part of the response returned to the client:

    delete cloneData.user.password;

What are the benefits of this approach?

Implementing password hashing offers several security benefits:

  • Risk reduction: Minimizes the risk of exposure in the event of data breaches.
  • Regulatory compliance: Helps comply with data protection regulations that require protection of sensitive information.
  • User confidence: Improves user confidence in the secure handling of their personal data.

Upon completion of the hashing configuration for detailed implementation, you are ready to proceed to develop advanced functionalities such as login and authentication endpoints based on secure tokens. It is always essential to be one step ahead in security issues within your API.

Contributions 29

Questions 10

Sort by:

Want to see more contributions, questions and answers from the community?

Pueden utlizar los hooks con sequelize para que realice el hash de la contraseña antes de guardar los datos. Solo tienes que agregar la opción hooks en el método config de la clase User que se encuentra en user.model.js

static config(sequelize) {
    return {
      sequelize,
      tableName: USER_TABLE,
      modelName: 'User',
      timestamps: false,
      hooks: {
        beforeCreate: async (user, options) => {
          const password = await bcrypt.hash(user.password, 10);
          user.password = password;
        },
      }
    };
  }

De esta forma puedes evitar realizar el hash en los servicios user y customer y dejarlos como estaban anteriormente.

Resolviendo Reto

Para poder resolver este reto lo que debemos de hacer entrar al objeto de dataValues luego al objeto de usuario y por ultimo al objeto de usuario. Y ya podemos acceder a la propiedad de contraseña para proceder a borrarla.

Codigo

delete newCustomer.dataValues.user.dataValues.password;
return newCustomer;

Hi! I’m practicing my english. So I will post my notes:

There are too many ways to encrypt passwords, but we are going to do it through models hooks.

To encrypt a password before being send to the database, just add a hook in model.config

static config(sequelize) {
        return {
            sequelize,
            tableName: USER_TABLE,
            modelName: 'User',
            timestamps: false,
            **hooks: {
                beforeCreate: async(user) => {
                    const password = await bcrypt.hash(user.password, 10);
                    user.password = password;
                },
            }**
        }
    }

(This part of the code was made by @Carlos Alberto Angel Angel, some comments above this)

This is good! But when we create a password, user will receive the hash too, so we need to delete it from the info that will be sent.

In user.service: We delete password from dataValues when object is created

async create(data) {

        const newUser = await models.User.create(data);
        //Delete password to don't send it when created
        **delete newUser.dataValues.password;**
        return newUser;
    }

Se debe hacer hashing por el lado de users y customers para guardar la contraseña hasheada en la base de datos, para ello modificamos la parte del servicio en ambos lados.

user.service.js, en esta parte se usa delete newUser.dataValues.password para que la respuesta no devuelva la password al crear un usuario:

async create(data) {
    const hash = await bcrypt.hash(data.password, 10);
    const newUser = await models.User.create({
      ...data,
      password: hash,
    });
    delete newUser.dataValues.password;
    return newUser;
  }

customer.service.js, lo que se hace es clonar la información (data) y al subobjeto le pasamos la información y específicamente el password (hash). Se usa delete newCustomer.user.dataValues.password para que la respuesta no devuelva la password al crear un cliente (ligado a usuario):

async create(data) {
    const hash = await bcrypt.hash(data.user.password, 10);
    const newData = {
      ...data,
      user: {
        ...data.user,
        password: hash,
      },
    };
    const newCustomer = await models.Customer.create(newData, {
      include: ['user'],
    });
    delete newCustomer.user.dataValues.password;
    return newCustomer;
  }

Si usas sequelize, mejor usa la serialización que viene por defecto para excluir atributos, y el scope para poder usar todos:

{
    sequelize,
    modelName: 'User',
    tableName: 'users',
    defaultScope: {
      attributes: {
        exclude: ['password']
      }
    },
    scopes: {
      allProperties: {
        attributes: ['id', 'email', 'name','password',]
      }
    }
  }

Podemos excluir el password de las consultas que incluyan el User cambiando el defaultScope, y si necesitamos hacer una consulta con el password podemos crear otro scope

static config(sequelize){
    return {
      sequelize,
      tableName: USER_TABLE,
      modelName: 'User',
      timestamps: false,
      hooks: {
        beforeCreate: async (user) => {
          const password = await bcrypt.hash(user.password, 10);
          user.password = password;
        },
      },
      defaultScope: {
        attributes: { exclude: ['password'] },
      },
      scopes: {
        withPassword:{ attributes: {}, }
      },
    }
  }

Para realizar las consulta con el scope withPassword:

const user = await models.User.scope("withPassword").findByPk(id);

Para eliminar el password de la respuesta, simplemente colocar esto antes del return:

  delete newCustomer.user.dataValues.password;
  return newCustomer;
}

➕# Clase #6: Implementando hashing para usuarios 6/20 ➕
 

Continuando con el Proyecto: 📝

 

  • Se va a realizar el hashing tanto para los usuarios (users) y clientes (customers), para ello vamos a VSC, buscamos la carpeta services, luego abrimos el archivo user.service.js, se modificará la función de crear usuario, se necesitará importar en la cabecera el bcrypt para la encriptación:
     
const bcrypt = require('bcrypt');

 
La función create(data) queda:
 

async create(data) {
    //El password viene de data.password
    const hash = await bcrypt.hash(data.password, 10);
    const newUser = await models.User.create({
      //Se clona todo el objeto con data
      ...data,
      //Se asigna el password
      password: hash
    });
    //Con el delete se elimina el password de la respuesta para no mostrar el password en pantalla
    //Lo elimina de dataValues
    delete newUser.dataValues.password;
    return newUser;
  }

 

  • Guardamos, vamos a la terminal y en caso de que no se haya levantado docker ni pgAdmin, hacerlo con:
     
docker-compose up -d postgres

docker-compose up -d pgadmin

 
Luego correr con dev:
 

npm run dev

 

  • Abrir Insomnia, e ir a la carpeta users, con la opción dev para la dirección de la API, vamos al POST de users y colocamos en el JSON:
     
{
	"email": "admin @ mail. com",
	"password": "12356 .2019",
	"role": "admin"
}

 
Al enviar (Send) en la salida debe estar el código 201 Created con:
 

{
	"createdAt": "2023-03-16T23:16:53.124Z",
	"id": 1,
	"email": "admin @ mail. com",
	"role": "admin"
}

 
Nota: No sale el password impreso ya que se agregó el código el método delete para borrar en la respuesta el hash del password.
 
En cuanto a customer, dentro de la carpeta services la función create(data) queda:
 

async create(data) {
    //Se crea el hash
    const hash = await bcrypt.hash(data.user.password, 10);
    //Se hace un clonación de data
    const newData = {
      ...data,
      //Se sobreescribe el sujeto user
      user: {
        ...data.user,
        //Se modifica el password
        password: hash
      }
    }
    const newCustomer = await models.Customer.create(newData, {
      include: ['user']
    });
    delete newCustomer.dataValues.user.dataValues.password;
    return newCustomer;
  }

 
Nota: Tomar en cuenta que en éste caso tenemos anidado la creación de un customer.
 

  • Guardamos, con la terminal corriendo: npm run dev
     
  • Vamos a Insomnia, ir a la carpeta customers, con la opción dev para la dirección de la API, vamos al POST de customers y colocamos en el JSON:
     
{
	"name": "Santiago",
	"lastName": "Molina",
	"phone": "9898",
	"user": {
		"email": "santi @ mail.
com",
		"password": "23 23 23"
	}
}

 
Al enviar (Send) en la salida debe estar el código 201 Created con:
 

{
	"createdAt": "2023-03-16T23:37:01.327Z",
	"id": 1,
	"name": "Santiago",
	"lastName": "Molina",
	"phone": "9898",
	"user": {
		"role": "customer",
		"createdAt": "2023-03-16T23:37:01.328Z",
		"id": 2,
		"email": "santi @ mail. com"
	},
	"userId": 2
}

 
Nota: No sale el password impreso ya que se agregó el código el método delete para borrar en la respuesta el hash del password.
 

entiendo que el hash para un valor determinado es siempre el mismo, por lo tanto si dos usuarios tienen la misma contraseña el hash sería el mismo, esto hace vulnerable a la aplicación porque sería posible de cierta manera identificar los usuarios que tienen la misma contraseña. Sugiero que al construir el hash se concatene el nombre de usuario al hash y con esto se obtiene un hash único por usuario aunque tengan claves iguales.

Yo lo hice con MongoDB y para eliminar el campo de password en las solicitudes es tan sencillo como lo siguiente:

const userSchema = new Schema(
  {
    email: {
      type: String,
      required: true,
      unique: true,
    },
    password: {
      type: String,
      required: true,
    },
  },
  { timestamps: true }
);

userSchema.methods.toJSON = function () {
  const user = this.toObject();
  delete user.password;
  return user;
}

const User = mongoose.model('User', userSchema);

module.exports = User;

El tema de eliminar el password es interesante pero yo vengo un poco de django y con rest_framework, y en los serializaers podemos definir que campos mostrar, y sequelize tiene esta opción, por lo que muestro mi código para que conozcan un poco mas…!

User Service

// Crea un nuevo usuario y lo retorna
  async create(body) {
    let password_hashed = await bcrypt.hash(body.password, 10)
    body.password = password_hashed
    const newUser = await models.User.create(body);
    if (body.profile) {
      const profile = await models.Profile.create({...body.profile, userId: newUser.id})
    }
    if (body.address) {
      const address = await models.Address.create({...body.address, userId: newUser.id})
    }
    return await this.getOne(newUser.id);
  }
// Retorna un usuario por su primaryKey
  async getOne(id) {
    const user = await models.User.findByPk(id, {
      attributes: ['id', 'email', 'role', 'isActive', 'isAdmin', 'isStaff'],
      include: [
        {
          association: 'profile',
          attributes: ['name', 'lastName', 'image', 'phone']
        },
        {
          association: 'address',
          attributes: ["postalCode", "country", "city", "description", "reference"]
        }
      ],
    })
    if (!user) {
      throw boom.notFound(`User with id:${id} not exits..!`)
    }
    return user
  }

En donde no se si esta mal realizar otra petición, sin embargo creo que era lo mejor para reducir código…!

Yo no use customer al contrario use Profile

Profile Service

 // Crea un nuevo perfil y lo retorna
  async create(body) {
    if (body.user) {
      let password_hashed = await bcrypt.hash(body.user.password, 10)
      body.user.password = password_hashed
    }
    const newProfile = await models.Profile.create(body, {
      attributes: ['id', 'name', 'lastName', 'image', 'phone', 'userId'],
      include: ['user']
    });
    return await this.getOne(newProfile.id);
  }

  // Retorna un perfil por su primaryKey
  async getOne(id) {
    const profile = await models.Profile.findByPk(id, {
      attributes: ['id', 'name', 'lastName', 'image', 'phone', 'userId'],
      include: {
        association: 'user',
        attributes: ['id', 'email', 'role', 'isActive', 'isAdmin', 'isStaff']
      }
    });
    if (!profile) {
      throw boom.notFound(`Profile with id:${id} not exits..!`);
    };
    return profile
  }

Como se observa le podemos decir que atributos o campos mostrar y de esta misma manera para los includes…!
Espero a alguien le sirva…!

Eliminar el password de la respuesta =>

delete newCustomer.user.dataValues.password;
delete newCustomer.dataValues.user.dataValues.password;

Esta fue la manera en que elimine yo la propiedad ‘password’, este código pertenece a mi servicio de ‘customers’.

  async findAll() {
    const rta = await models.Customer.findAll({
      include: [
        {
          association: "user",
          attributes: { exclude: ["createdAt", "updatedAt", "password"] },
        },
      ],
      attributes: { exclude: ["createdAt", "updatedAt"] },
    });
    return rta;
  }

  async findOne(id) {
    const rta = await models.Customer.findByPk(id, {
      include: [{ association: "user", attributes: { exclude: "password" } }],
    });
    if (!rta) throw boom.notFound("Customer not Found");
    return rta;
  }

En mi caso yo creo a los ‘customers’ desde ‘users’

sencillamente agregue el user antes del dataValues y funciono

este es el resultado

En lugar de eliminar manualmente el atributo `password` con: ```js delete newUser.dataValues.password ```puedes aprovechar la funcionalidad nativa que ofrecen tanto SQL como Sequelize. **Con SQL**, por ejemplo, puedes insertar una fila y que se retornen solo los campos deseados: ```js INSERT INTO productos (email, password, role) VALUES ('[email protected]', 'password123', 'admin') RETURNING email, role; ```De esta forma, la base de datos se encarga de devolver únicamente los campos `email` y `role`, excluyendo `password`. **Con Sequelize**, puedes lograr un comportamiento similar al crear un nuevo usuario. Al utilizar la opción `attributes` con `exclude`, indicas que deseas omitir el atributo `password` en el objeto resultante: ```js const newUser = await models.User.create({ ...data, password: hash },{ attributes: { exclude: ['password'] } }) ```Este enfoque te permite evitar la manipulación manual del objeto devuelto, confiando en las funcionalidades nativas de Sequelize y de la base de datos para gestionar los campos que deseas incluir en la respuesta.
Me esta gustando mucho este curso
para el reto de eliminar el passwor lo realize de la siguiente manera: ```js async create(data) { const hash = await bcrypt.hash(data.user.password, 10); const newData = { ...data, user: { ...data.user, password: hash, }, }; const newCustomer = await models.Customer.create(newData, { include: ['user'], }); delete newCustomer.dataValues.user.dataValues.password; return newCustomer; } ```async create(data) {    const hash = await bcrypt.hash(data.user.password, 10);    const newData = {      ...data,      user: {        ...data.user,        password: hash,      },    };    const newCustomer = await models.Customer.create(newData, {      include: \['user'],    });     delete newCustomer.dataValues.user.dataValues.password;    return newCustomer;  }
Para aportar a los compañeros que ya explicaron el hook de **beforeCreate** para encriptar la contraseña, les comparto el hook **afterCreate**: ```js afterCreate: async (user) => { delete user.dataValues.password; }, ```Con este pueden eliminar directamente el atributo **password** retornado, Esto no elimina el valor en el campo (porque ya se izo el insert) y permite definir una única regla en el modelo sin tener que estar eliminando la propiedad desde los diferente servicios que usen el modelo

Una manera más sencilla para hacer que la contraseña tenga el hash para la entidad de Customer, puede ser de la siguiente manera:

data.user.password = await bcrypt.hash(data.user.password, 10)

mi solucion al reto fue el siguiente

delete newCustomer.user.dataValues.password;

en el cual especifico que entro al objeto user y luego en sus valores al password

Si usamos Typescript para hacer nuestra API no es tan sencillo usar delete para eliminar el password, yo cuando me enfrente a ese reto lo que hice fue setear la propiedad password a password: ''

Para que no se vean las contraseñas al momento de hacer GET de los clientes y de los usuarios, debemos de excluir ese atributo mediante Sequelize. Se realiza de la siguiente manera:

Nuestros métodos findUsers y findOneUser o como el profesor lo tiene en el curso: find y findOne, quedaría así:

// user.service.js
async findUsers () {
    const users = await models.User.findAll({
      attributes: {
        exclude: ['password']
      },
      include: ['customer']
    })
    return users
  }

async findOneUser (id) {
    const user = await models.User.findByPk(id, {
      attributes: {
        exclude: ['password']
      }
    })
    if (!user) {
      throw boom.notFound('User not found')
    }
    return user
  }

Y nuestro método findCustomers o como el profesor lo tiene en el curso: find, quedaría así:

// customer.service.js
async findCustomers () {
    const customers = await models.Customer.findAll({
      include: [{
        model: User,
        as: 'user',
        attributes: {
          exclude: ['password']
        }
      }]
    })
    return customers
  }

a ver el tema de hacer el delete dentro del create está bien, pero me chilla que estamos haciendo otra cosa más allá de crear dentro de método(por el tema de la S de los principios SOLID), tal vez si separamos esa lógica en un mapper sería mejor, idk solo quería comentarlo 😁

delete newCustomer.user.dataValues.password

Si utilizas Mongoose (MongoDB), para eliminar la propiedad, hay que acceder a ._doc:

const dbRes = await newUser.save();
    delete dbRes._doc.password;
    return dbRes;

Saludos!

No se si tendrá un efecto práctico pero tengo duda sobre como quitar los hash en el service get de los clientes. No se si en include[iser] se puede limitar que solo traiga ‘x’ datos

No se me había ocurrido hashear los usuarios

Reto conseguido!