Back to blog
Feb 04, 2020
10 min read

Login y register con nodejs, express, JWT y mongoDB

Una guía completa sobre cómo implementar un login y register con Node.js, Express, JWT y MongoDB, explicando cada paso con ejemplos prácticos y código fuente.

Todos alguna vez hemos tenido la necesidad de hacer un login y register con un lenguaje de programación en especial, aunque tengamos experiencia en programación nos toca que abrir la documentación del nuevo lenguaje de estudio y nos toca que googlear; sobre todo, cuando la documentación se nos es difícil entender.

Así que es seguro que has llegado a este post debido a eso; y te invito a que termines la lectura y cualquier duda, recomendación o crítica es bienvenida :)

Antes que antes que nada, te invito a que le eches un ojo a un post anterior donde explico de forma breve cómo funciona JWT

[Vuejs y JWT

Seguramente eres un usuario ya experimentado o simplemente un nuevo usuario que está aprendiendo Vuejs; este famoso y…

Antes de empezar, echemos un ojo a la estructura de carpetas de nuestro pequeño ejemplo de login y register con nodejs

captionless image

Nodejs nos provee la facilidad de que agreguemos solo lo que necesitamos, con pocas lineas de código ya tenemos un servidor en nuestro pc.

  • La carpeta public, es la carpeta que estará disponible para todo el mundo cuando entremos a localhost:3000 (local) en nuestro caso, un simple archivo html.
  • Server es la carpeta que contiene todo el código que no está disponible para cualquiera, es decir, es el código que ejecutará nuestro servidor

Dentro de la carpeta server tenemos los modelos, que manejaremos con mongoose, la carpeta encargada de las rutas, y la carpeta config, que puede contener la configuración de nuestro proyecto, en este caso, las variables de entorno manejadas con process de nodejs; y por último nuestro archivo principal server.js que es el archivo que une toda nuestra app.

Antes que nada tenemos que tener presente que necesitamos de tres componentes básicos para el funcionamiento.

  • Un modelo: que se encarga de la gestión de los usuarios en mongodb
  • Rutas: que son las encargadas de recibir la información enviada desde nuestro frontend y de devolvernos la información que genera nuestro backend.
  • Conexión a la db: en este caso, con mongodb, aunque fácilmente puede ser con otro tipo de db, incluso Redis, mySql, o la que te sea conveniente.

Con estas tres cosas presentes en nuestro proyecto podremos hacer un sencillo, funcional y seguro login y register.

Empecemos con el código

En nuestro archivo server/server.js consta de varias partes las cuales, las explico de forma breve

Exportamos nuestra dependencias al inicio del archivo. Estas dependencias serán las que nos ayuden al funcionamiento correcto del server.

require('./config/config');
const express = require('express')
const app = express()
const mongoose = require('mongoose');
const bodyParser = require('body-parser')
const path = require('path');

El siguiente código es básicamente un middleware que nos ayuda a “parsear” los datos que recibimos a través del protocolo http.

PD: para más información leer la descripción de npm

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))// parse application/json
app.use(bodyParser.json())

Como buenas prácticas se nos recomienda separar nuestro proyecto por partes, en este caso, las rutas, es decir; los controladores deben de tener su propio archivo y exportarlos como un “middleware” al archivo server.js

// Configuracion global de rutas
app.use(require('./routes/index'));

Las siguientes líneas nos indica una ruta raíz, es decir, al momento que vamos a localhost:3000 nodejs nos servirá un archivo html sencillo.

Para más información vista la documentación oficial

let renderHTML = path.resolve(__dirname, '../public/index.html');app.get('/', function (req, res) {    res.sendFile(renderHTML);})

La conexión a nuestra db no debe de faltar

mongoose.connect(process.env.URLDB, {
    useNewUrlParser: true,
    useCreateIndex: true,
    useUnifiedTopology: true}, (err) => {    if (err) throw err;
        
    console.log("Base de datos online");});

Process.env.URLDB y process.env.PORT son una variable de entorno que veremos más adelante.

Y por último, el código para lanzar nuestro servidor

app.listen(process.env.PORT, ()=> {
    console.log("Escuchando en puerto 3000");
})

Nuestro archivo server.js nos quedaría de la siguiente manera.

Server.js

Creando nuestro archivo de configuración

todo proyecto web, requiere de configuraciones básicas disponibles desde cualquier lado de nuestro proyecto, y la mayoría de entornos de desarrollo contienen estas variables de configuración, como en laravel, tenemos un archivo llamado .env, y en nodejs no es la excepción; a estas configuraciones se les conoce como variables de entorno, y es acá donde definimos por ejemplo, nuestra cadena de conexión, un seed de encriptación de contraseña; entre otros.

Dentro de la carpeta /config/config.js definimos:

  • Puerto de acceso desde nuestro localhost
process.env.PORT = process.env.PORT || 3000;
  • Entorno de desarrollo (dev, local, prod, test)
process.env.NODE_ENV = process.env.NODE_ENV || 'dev';
  • Cadena de conexión a nuestra bd.
let urlDB = "";if (process.env.NODE_ENV === 'dev') {
    urlDB = "mongodb://localhost:27017/mediumNodeLogin";
} else {
    urlDB = "here write the mongo connection with mongo atlas and      other type of connection mode"
};process.env.URLDB = urlDB;
  • Caducidad del token
process.env.CADUCIDAD_TOKEN = '48h';
  • Seeds de autenticación (si deseas saber más sobre este tema visita: click aqui)
process.env.SEED_AUTENTICACION = process.env.SEED_AUTENTICACION ||  'este-es-el-seed-desarrollo';

Al final nos quedara un archivo de configuración así:

captionless image

Creando modelo Usuario

Los modelos los podemos nombrar con cualquier nombre, en este caso, modelo Usuario; que facilmente podría llamarse modelo User, o como se les sea más conveniente.

Empezamos importando los archivos js que requerimos para hacer un modelo con mongoose

const mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');

la importación de uniqueValidator lo que hace es facilitarnos de escribir por nosotros mismos la validación de que un campo sea único, en este caso, el campo email, email es de suma importancia que sea único en nuestro modelo Usuario.

Por si desean saber más sobre cómo funciona visitar la documentación de npm

Continuamos definiendo un Object que nos ayude a validar que tipo de rol será válido para usuario. Por ejemplo, queremos asegurarnos de que solo existan dos tipos de roles, ADMIN Y USER y que así, evitamos que nos envíen un role que no esta permitido en nuestra db, por ejemplo el role CONSERJE.

Además, definimos un Schema de mongoose.

let rolesValidos = {    values: ["ADMIN", "USER"],
    message: '{VALUE} no es un role válido'}
let Schema = mongoose.Schema;

Luego continuamos definiendo un nuevo Schema; recuerda que acá es donde definimos los campos de nuestro document de mongodb; es decir que si queremos que exista el campo por poner un ejemplo de estado de si está o no activo el usuario; debe de ir dentro del modelo Usuario

let usuarioSchema = new Schema({nombre: {
    type: String,
    required: [true, 'El nombre es necesario'],},email: {
    type: String,
    unique: true,
    required: [true, "El correo es necesario"],
},password: {
    type: String,
    required: [true, "Le contraseña es obligatoria"],},role: {
    type: String,
    default: 'USER',
    required: [true],
    enum: rolesValidos,},});

Ahora, el modelo Usuarios también se encarga de traernos la data de cada usuario registrado al momento de hacer login; en este caso, nos devolvería toda la información del modelo, incluyendo la contraseña, aunque la contraseña estará encriptada con un hash jwt ¿queremos que cualquier usuario tenga acceso a la cadena encriptada de nuestros usuarios?

La respuesta es un rotundo no, por lo cual, al momento de hacer login, tenemos que asegurarnos que este campo no nos lo devuelva, motivo por el cual, tendremos que eliminarlo de la response de la petición http

// elimina la key password del objeto que retorna al momento de crear un usuariousuarioSchema.methods.toJSON = function() {   let user = this;
   let userObject = user.toObject();
   delete userObject.password;   return userObject;}

y por último, agregamos el plugin de validación única y exportamos el modelo recién creado

usuarioSchema.plugin(uniqueValidator, {
    message: '{PATH} debe de ser único'
})
module.exports = mongoose.model('Usuario', usuarioSchema)

Al final nos quedaría un modelo como el siguiente

Usuario.js

Rutas de nuestra app

Como se observó en la estructura de carpetas, dentro de la carpeta routes contamos con 3 archivos, el index, que es el archivo principal donde importamos el resto de rutas; seguido del archivo login.js y register.js que contiene el código pertinente para hacer login y register.

Nuestro archivo /routes/index.js contiene el siguiente código

const express = require('express')
const app = express()app.use(require('./login'));
app.use(require('./register'));module.exports = app;

Importamos express y app para hacer uso del middleware .use y al final, exportamos app con module.exports

Ruta de login

Empezamos importando a nuestro archivo /routes/login.js las dependencias que se requieren para el funcionamiento; en este caso ya requerimos del modelo recién creado; ya que es a través de este modelo que vamos a poder guardar, editar, buscar, y listas los usuarios registrados.

const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const Usuario = require('./../models/usuario');
const app = express();

Es de suma importancia que nuestros modelos sean asignados con la primera letra en mayúscula, esto para que podamos diferenciar el uso del modelo en el código.

Las rutas se crean con la siguiente sintaxis:

app.post('/login', function (req, res) {    // here write code
})

donde app es la referencia a express y .post es el verbo http que requerimos para está ruta, es decir, que si deseamos un get, un update o delete, .post debe de ser reemplazado por cualquier otro verbo requerido.

dentro recibe dos parámetros (en este caso) el primero ‘/login’ que es el nombre que le asignamos a la ruta, y como segundo parámetro, recibe una función donde se recibe como parametro req y res, req es la encargada de recibir la data proveniente del frontend, y res es quien se encarga de devolvernos un status code y/o header que establezcamos.

Dentro de la ruta, debemos de obtener la información proveniente del frontend.

let body = req.body;

y guardamos la información que recibimos.

Usuario.findOne({ email: body.email }, (erro, usuarioDB)=>{     if (erro) {
       return res.status(500).json({
          ok: false,
          err: erro
       })
    }// Verifica que exista un usuario con el mail escrita por el usuario.   if (!usuarioDB) {      return res.status(400).json({
        ok: false,
        err: {
            message: "Usuario o contraseña incorrectos"
        }
     })
   }// Valida que la contraseña escrita por el usuario, sea la almacenada en la db   if (! bcrypt.compareSync(body.password, usuarioDB.password)){      return res.status(400).json({
         ok: false,
         err: {
           message: "Usuario o contraseña incorrectos"
         }      });   }// Genera el token de autenticación    let token = jwt.sign({
           usuario: usuarioDB,
        }, process.env.SEED_AUTENTICACION, {
        expiresIn: process.env.CADUCIDAD_TOKEN
    })    res.json({
        ok: true,
        usuario: usuarioDB,
        token,
    })
})
```js

y al finalizar, exportamos nuevamente app

```js
module.exports = app;

Al finalizar, nos quedará una ruta como esta:

captionless image

Ruta register

La ruta register es similar a la ruta login, a diferencia que acá es donde se tiene que encriptar la contraseña, y es donde se hace uso de bcrypt

const express = require('express');
const bcrypt = require('bcrypt');
const Usuario = require('./../models/usuario');
const app = express();app.post('/register', function (req, res) {  let body = req.body;
  let { nombre, email, password, role } = body;  let usuario = new Usuario({
    nombre,
    email,
    password: bcrypt.hashSync(password, 10),
    role
  });usuario.save((err, usuarioDB) => {    if (err) {
      return res.status(400).json({
         ok: false,
         err,
      });
    }    res.json({
          ok: true,
          usuario: usuarioDB
       });
    })
});module.exports = app;

Al final, nos quedará algo como esto:

captionless image

Si probamos nuestro login con postman nos debe de devolver algo similar a:

Login

captionless image

Register

captionless image

Error register

captionless image

Y ya tenemos listo un login y register usando nodejs, express, jwt y Mongodb :)