Implementar JSON Web Tokens con NodeJS

implementando JSON Web Tokens con NodeJS - Cuarta parteJSON Web Tokens o simplemente JTW, es una herramienta que nos permite autenticarnos con el servidor mediante Tokens de una forma simple y segura. Los Tokens son una cadena de alfanumérica, que es generada por el servidor y es enviada al cliente para autenticaciones futuras, evitando tener que enviar credenciales en cada invocación.

Te recomiendo que te des una venta a mi artículo Autenticación con JSON Web Tokens, el cual abordo toda la teoría, por si no estás familiarizado con el concepto de Tokens y la forma de trabajar.

Este artículo es parte de una colección de artículo en donde explico cómo Construir un API REST con NodeJS, por lo que si estas interesado puede regresar desde el comienzo.

Implementando JWT en un API REST

Lo primero será instalar el módulo de JWT, para lo cual, ejecutaremos el comando npm install –save jsonwebtoken body-parser , una vez hecho esto, tendremos que implementar un servicio de autenticación tradicional, el cual reciba usuario y contraseña y nos regrese un token en caso de éxito. El servicio quedaría de la siguiente manera:

var jwt = require('jsonwebtoken')
var bodyParser = require('body-parser')

app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.json({limit:'10mb'}))

app.post('/login', (req, res) => {
  var username = req.body.user
  var password = req.body.password

  if( !(username === 'oscar' && password === '1234')){
    res.status(401).send({
      error: 'usuario o contraseña inválidos'
    })
    return
  }

  var tokenData = {
    username: username
    // ANY DATA
  }

  var token = jwt.sign(tokenData, 'Secret Password', {
     expiresIn: 60 * 60 * 24 // expires in 24 hours
  })

  res.send({
    token
  })
})

La función login recibe como parámetro el usuario (req.body.user ) y el password (req.body.password ), los cuales los tendremos que validar contra nuestros usuarios, sin embargo, en este caso, para hacerlo más simple, lo hemos validado contra el usuario oscar/1234, si la autenticación falla, entonces regresamos un error al cliente. Por otra parte, si todo sale bien, creamos un token al usuario, el cual tiene una vigencia de 24 horas, para finalmente retornarlo.

Veamos un ejemplo de autenticación:

Creando el Token JWS

En la imagen pasada hemos enviado las credenciales correctas para que nos genere un token, sin embargo, no se puede ver correctamente debido a su longitud, es por ello que lo ponemos por a continuación:

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im9zY2FyIiwiaWF0IjoxNTE2MDY4OTI4LCJleHAiOjE1MTYxNTUzMjh9.zucLW085AiZ8VWojwNFFcMz0yv1H4RbeCMQy7lVjS7s"
}

Este token deberá ser guardado por el usuario para futuras invocaciones al API.

Autenticándonos mediante Tokens

El siguiente paso será crear un servicio protegido que valida la existencia del token, de lo contrario, evitará el acceso, para ello crearemos un servicio de prueba llamado secure:

app.get('/secure', (req, res) => {
    var token = req.headers['authorization']
    if(!token){
        res.status(401).send({
          error: "Es necesario el token de autenticación"
        })
        return
    }

    token = token.replace('Bearer ', '')

    jwt.verify(token, 'Secret Password', function(err, user) {
      if (err) {
        res.status(401).send({
          error: 'Token inválido'
        })
      } else {
        res.send({
          message: 'Awwwww yeah!!!!'
        })
      }
    })
})

Como podemos observar, lo primero que tenemos que hacer, es recuperar el header correspondiente al token, es decir, el token “authorization ”, si este header no se encuentra, entonces retornamos un código 401 (No autorizado) y regresamos el motivo del error.

El siguiente paso es algo confuso, pues al token hay que retirarla el substring “Baerer  “, este String es parte de las especificaciones de HTTP, y es agredo de forma automática, por lo que no entraremos en detalles, simplemente lo retiramos.

Finalmente, validamos el token mediante el método verify, el cual validará que el token sea válido y además que no esté caducado, en cualquiera de estos dos casos, un error 401 es retornado junto con el mensaje de error. Finalmente, si todo sale bien, procedemos con la operación de negocio.

El siguiente request es enviado son el header, retornando un error 401 y su mensaje de error correspondiente:

Validacion fallida del token JWT

Este nuevo request es enviado con el header ‘authorization ’ el cual corresponde a un token válido generado por el servicio de login:

NOTA: Siempre será mejor usar un middleware para validar el token, de esta forma, evitamos tener que validar el token en cada invocación, sin embargo, lo hemos hecho así para hacer más fácil el ejemplo.

12 thoughts to “Implementar JSON Web Tokens con NodeJS”

  1. Observación en el primer código le hace falta esto:

    Instalar body parser:
    npm install -save body-parser
    e invocarlo:
    var bodyParser = require(‘body-parser’);

    Duda, si yo quiero tener un JS separado del server.js para introducir el código del JWT para luego nada mas invocarlo en mis servicios rest, ¿como sería?

    1. Gracias por las observaciones, las tomaré en cuenta.

      Con respecto a tu pregunta, eso es muy simple, solo creas las funciones que creas necesarias en un archivos JS separado, y al final del archivo las exportas (module.exports) y luego solo importas el archivos JS desde server.js y utilizas la función.

      saludos.

  2. Gracias por esta implementación, muy clara e útil.
    Pero no entendí la nota final ” Siempre será mejor usar un middleware para validar el token, de esta forma, evitamos tener que validar el token en cada autenticación”.

    1. Hola Juan, primero que nada, te comento que el párrafo estaba mal parafraseado, la redacción correcta sería “evitamos tener que validar el token en cada invocación“, dicho esto, lo que quiero decir es que mediante un middleware, podemos interceptar todas las invocaciones y validar el token en un solo punto, en otro caso, tendríamos que implementar la lógica de validación en cada uno de los servicios.

  3. Un ejemplo de middleware para evitar la validación al consumir cada servicio. Como seria? Puede explicar en por qué usar middleware

    1. Hola José, lo que puedes hacer es implementar tu propio Middleware donde tu mismo puedes poner la lógica para validar si un servicio debe o no tener seguridad, en mi libro de “Aplicaciones reactivas con React, NodeJS y MongoDB” explico esto, puedes ver como implemente esta lógica en el siguiente archivos JS, donde indico que todas las rutas que comiencen con /secure deberán tener token forzosamente, y el resto podrán continuar son tokenn.
      La liga al codigo es: https://github.com/oscarjb1/books-reactiveprogramming/blob/Capitulo-16-Produccion/api/api.js
      La liga a mi libro es: https://reactiveprogramming.io/books/applicaciones-reactivas-con-react-nodejs-mongodb
      saludos

  4. Buenas noche Oscar. Quería consultar cual es la herramienta gráfica que usas en la imagen para ver el token. donde colocas la ruta y los datos. gracias.

    1. Hola Nestor, la herramienta es Restlet, que en realidad es un plugin de chrome, muy bueno, tiene versión free y de paga, todo depende que tantas cosas requieras.
      saludos.

  5. Hola, excelente explicación. He estado navegando para encontrar respuesta a esta pregunta. (Soy novato)

    Encontré un ejemplo de cómo redireccionar a un usuario a mi página web después de loguearse con una cuenta microsoft, sin embargo me gustaría saber cómo valido que el token que me llego con el URL es correcto.

    Gracias

    1. Hola Juan, si te logeas con la cuenta de Microsoft es por que estas usando el estandar OAuth2, lo cual quiere decir que Microsfot tiene servicios para valida ese token, tendrías que ir a la documentación de Microsoft para saber cual es.

  6. Entiendo que debemos poner el jwt en el header y ustedes lo muestran, pero mi duda es como sería en una aplicación real, es decir como puedo automaticamente poner el jwt en el header?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *