Autenticación con JWT en JSON Server

Introducción

En este artículo te explico como crear un API Rest completa junto a faker.js para que puedas hacer tus pruebas sin muchas complicaciones.

Como ampliación me parece muy interesante añadir a nuestra API Rest seguridad, así que en este nuevo artículo vamos a usar JWT para agregar un sistema de autenticación por token.

Voy a dar por sentado que tanto json-server (imprescindible) como faker.js (opcional) está(n) instalado(s).

Ampliación JSON server

Quiero aprovechar para hacer una pequeña ampliación sobre algunas funciones avanzadas que ofrece json-server, porque en el artículo anterior solo probamos el CRUD.

Para no extenderme mucho voy añadir el código que genera los datos falsos y el indice con las funciones avanzadas que podemos hacer en JSON Server:

let faker = require('faker');
let generateData = () => {
    let data = []
    for (let id = 0; id < 1000; id++) {
        data.push({
            "id": id,
            "address": faker.address.streetAddress(),
            "latitude": faker.address.latitude(),
            "longitude": faker.address.longitude(),
            "first_name": faker.name.firstName()
        });
    }
    return { "data": data }
}
module.exports = generateData

/*
------------------------
FILTER
[
  {
    "id": 4,
    "address": "866 Mohr Light",
    "latitude": "-55.7908",
    "longitude": "170.3427",
    "first_name": "Tyree"
  }
]
CURL: http://localhost:3000/data?id=4
------------------------
------------------------
PAGINATE
[
  {
    "id": 10,
    "address": "612 Fahey Locks",
    "latitude": "57.2960",
    "longitude": "-54.5350",
    "first_name": "Antone"
  },
  {
    "id": 11,
    "address": "09094 Tillman Brooks",
    "latitude": "69.1174",
    "longitude": "-134.4193",
    "first_name": "Tito"
  },
  {
    "id": 12,
    "address": "8161 Kelton Shoal",
    "latitude": "86.5114",
    "longitude": "65.8792",
    "first_name": "Freda"
  },
  {
    "id": 13,
    "address": "21785 Jasmin Trail",
    "latitude": "-33.6972",
    "longitude": "16.9923",
    "first_name": "Bettye"
  },
  {
    "id": 14,
    "address": "620 Abernathy Ports",
    "latitude": "-9.2774",
    "longitude": "-47.5246",
    "first_name": "Braden"
  },
  {
    "id": 15,
    "address": "24260 Wilfred Parks",
    "latitude": "54.5527",
    "longitude": "111.0772",
    "first_name": "Roselyn"
  },
  {
    "id": 16,
    "address": "2826 Oral Trafficway",
    "latitude": "-61.6411",
    "longitude": "-124.6945",
    "first_name": "Maude"
  },
  {
    "id": 17,
    "address": "0318 Hessel Junction",
    "latitude": "-87.0497",
    "longitude": "147.6963",
    "first_name": "Allison"
  },
  {
    "id": 18,
    "address": "93964 Darion Lights",
    "latitude": "-82.7756",
    "longitude": "176.7674",
    "first_name": "Quincy"
  },
  {
    "id": 19,
    "address": "341 Akeem Way",
    "latitude": "11.6085",
    "longitude": "67.5816",
    "first_name": "Theresa"
  }
]
CURL: http://localhost:3000/data?_page=2
Por defecto son paginaciones de 10 en 10 registros
------------------------
------------------------
SORT
[
  ........
  {
    "id": 605,
    "address": "688 Shanna Via",
    "latitude": "55.7688",
    "longitude": "168.6224",
    "first_name": "Zoila"
  }
]
CURL: http://localhost:3000/data?_sort=first_name&order=asc
------------------------
*/

Para obtener mas información puedes ir a la siguiente url

Añadiendo la autenticación JWT

Lo primero que tenemos que hacer es instalar el paquete jsonwebtoken

npm i jsonwebtoken –save

Una vez terminada la instalación crearemos un fichero que usaremos configurar nuestra API REST junto a JWT. En el siguiente enlace puedes acceder al repositorio.

No obstante voy añadir el código completo con una pequeña explicación.

servidor.js

// Añadimos los módulos necesarios
const FS = require('fs');
const bodyParser = require('body-parser');
const jsonServer = require('json-server');
const JWT = require('jsonwebtoken');
const middlewares = jsonServer.defaults()

// Servidor Express
const server = jsonServer.create();

// Enrutador Express
const router = jsonServer.router('./db.json');

// Creamos un JSON con los usuarios (03f996214fba4a1d05a68b18fece8e71 == MD5 Hash 'usuarios' )
const userdb = JSON.parse(FS.readFileSync('./03f996214fba4a1d05a68b18fece8e71.json', 'UTF-8'));

// Middlewares predeterminados (logger, static, cors y no-cache)
server.use(middlewares)

// Parseo del body
server.use(jsonServer.bodyParser);

// Configuración TOKEN y duración
const SECRET_KEY = 'zxcasdqwe098765';
const expiresIn = '1h';

// Crear un TOKEN desde un payload 
createToken = (payload) => JWT.sign(payload, SECRET_KEY, { expiresIn });

// Verificar el TOKEN 
verifyToken = (token) => JWT.verify(token, SECRET_KEY, (err, decode) => decode !== undefined ? decode : err);

// Comprobamos si el usuario existe en nuestra 'base de datos'
isAuthenticated = ({ email, password }) => userdb.users.findIndex(user => user.email === email && user.password === password) !== - 1;

// Creamos un ENDPOINT para comprobar si el usuario existe y poder crear y enviar un TOKEN
server.post('/auth/login', (req, res) => {
    const { email, password } = req.body;
    if (isAuthenticated({ email, password }) === false) {
        const status = 401;
        const message = 'Contraseña y/o password incorrectos';
        res.status(status).json({ status, message })
        console.log(message);
        return;
    }
    const access_token = createToken({ email, password });
    res.status(200).json({ access_token })
});

// Añadir un middleware Express que verifique si el encabezado de autorización.
// Luego verificara si el token es válido para todas las rutas, excepto la ruta anterior, 
// ya que esta es la que usamos para iniciar sesión en los usuarios.
server.use(/^(?!\/auth).*$/, (req, res, next) => {
    if (req.headers.authorization === undefined || req.headers.authorization.split(' ')[0] !== 'Bearer') {
        const status = 401;
        const message = 'Header con autorización incorrecta';
        res.status(status).json({ status, message });
        console.log(message);
        return;
    }
    try {
        verifyToken(req.headers.authorization.split(' ')[1]);
        next();
    } catch (err) {
        const status = 401;
        const message = 'Error: TOKEN de acceso no válido';
        res.status(status).json({ status, message });
        console.log(message);
    }
})
server.use(router);

server.listen(3000, () => {
    console.log('API REST FUNCIONANDO')
});

El fichero para la base de datos lo he generado con el siguiente script en NodeJS (db.js) :

let faker = require('faker');
let data = { data: [] };
for (let id = 0; id < 1000; id++) {
    data.data.push({
        "id": id,
        "address": faker.address.streetAddress(),
        "latitude": faker.address.latitude(),
        "longitude": faker.address.longitude(),
        "first_name": faker.name.firstName()
    });
}

console.log(JSON.stringify(data));

Para generar nuestra base de datos usaremos el siguiente comando:

node db.js > db.json

Y para finalizar, la tabla de los usuarios es un fichero JSON con unos mínimos datos:

{
    "users": [
        {
            "id": 1,
            "name": "ivan",
            "email": "ivan@dominio.com",
            "password": "qwerty1234"
        },
        {
            "id": 2,
            "name": "rafael",
            "email": "rafael@dominio.es",
            "password": "asdfgh56789"
        }
    ]
}

Cuando tengamos todo el proyecto preparado usaremos el siguiente comando para iniciar nuestro servidor:

node servidor.js

A continuación unas pruebas del funcionamiento de JWT:

Ahora con el usuario y la contraseña validos: