Creando un api rest con node.js y mongodb - autenticación con tokens
En una entrada anterior os explicaba como crear un API rest en node.js bajo una BBDD mongodb. Hoy os voy a intentar explicar cómo aplicar autenticación usando tokens JWT, una forma ligera de poder aplicar seguridad a vuestra API.
Primero instalaremos passport.js en nuestro backend con el comando npm install passport. Esta librería nos permitirá realizar nuestro trabajo de una forma más rápida, puesto que nos simplificará muchísimo el desarrollo.
Passport nos permite utilizar diferentes mecanismos de autenticación (lo que él llama estrategias) para proteger las rutas de nuestra API. La estrategia que vamos a utilizar nosotros es la llamada passport-http-bearer basada en la utilización de Bearer tokens. Esta estrategia busca en la cabecera “Authorization” de la petición, un token con la información del usuario.
Instalación:
Para instalar este módulo utilizamos la orden npm install passport-http-bearer
Configuración:
Las estrategias y su configuración se definen usando la función use().
passport.use(new BearerStrategy(
function(token, done)
{
/*LOGICA PARA OBTENER LOS DATOS DEL USUARIO EN BASE AL TOKEN*/
if (err) { return done(err); }
if (!user) { return done(null, false); }
return done(null, user, { scope: 'all' });
}
));
En este caso, al decirle que usamos la estrategia BearerStrategy, Passport implementa él solo toda la lógica encargada de obtener el token de la cabecera, leerlo y devolver un error en caso de no encontrarlo o que sea incorrecto. Si no usásemos esta librería tendríamos que realizar nosotros la lógica de buscar el token en la cabecera, parsearlo, descodificarlo, etc…
Passport usa lo que él denomina “verify callback”, que es precisamente, el encargado de obtener los datos del usuario mediante las credenciales suministradas, en este caso, el token.
passport.use(new BearerStrategy(
/*VERIFY CALLBACK*/
function(token, done)
{
}
));
Aquí viene la parte que nosotros tendremos que desarrollar. Necesitaremos pues, implementar el código que compruebe si esas credenciales (el token) son válidas, de ser así, invocaremos al método done(), suministrándole a Passport el usuario, si no, lo invocaremos enviando el parámetro false en lugar del usuario, para indicar a Passport que ha habido un error de autenticación.
var passport = require('passport'); var BearerStrategy = require('passport-http-bearer').Strategy; var User = require('../models/user'); passport.use(new BearerStrategy( function(token, done) { /*CODIGO A IMPLEMENTAR POR EL DESARROLLADOR*/ User.findOne({ token: token }, function (err, user) { /*ERROR */ if (err) { return done(err); } /*CREDENCIALES NO VALIDAS */ if (!user) { return done(null, false); } /*CREDENCIALES VALIDAS */ return done(null, user, { scope: 'all' }); }); } ));
Como veis en este sencillo código, tenemos un ejemplo básico, en el cual buscamos en nuestro esquema un usuario con ese token, si se encuentra lo devolvemos, de no ser así se invocamos done() con el parámetro false, indicando que ha sucedido un error de autenticación.
Para simplificar esta última parte y no tener que guardar los tokens en nuestro esquema de base de datos, vamos a utilizar JWT. JWT son las siglas de “JSON Web Tokens” un standard utilizado para crear estos tokens, en formato JSON.
El código ejecutado para comprobar el acceso a un usuario, quedaría finalmente de la siguiente forma:
var passport = require('passport');
var BearerStrategy = require('passport-http-bearer').Strategy;
var jwt = require('jsonwebtoken');
var moment = require('moment');
passport.use(new BearerStrategy(function(accessToken, next)
{
/*COMPROBACION DEL TOKEN*/
jwt.verify(accessToken, 'secret',{algorithm:'HS256'},
function(err,decoded)
{
if(err)
/*TOKEN INCORRECTO*/
return next(err);
else
{
if (decoded.exp <= moment().unix())
{
/* TOKEN EXPIRADO*/
next(null, false, { message: 'Token expired' });
}
/*TOKEN CORRECTO*/
next(null, decoded, { scope: '*' });
}
});
}
));
Resumiendo un poco estas líneas, primero, utilizamos la instrucción jwt.verify para comprobar que el token es efectivamente un token jwt,firmado con una clave privada, en este caso, ‘secret’. En las siguientes lineas se comprueba que el token no esté expirado, observando su propiedad ‘exp’, de cumplirse las dos premisas anteriores se devuelve el usuario, permitiendo el acceso al recurso solicitado.
El siguiente paso es indicar en nuestra aplicación, las rutas a proteger mediante esta estrategia:
router.get('/api/restrictedapi',
passport.authenticate('bearer', { session: false }),
function(req, res)
{
res.json(req.user);
});
De esta manera le indicamos a nuestra aplicación que cuando se produzca una petición a nuestro endpoint “api/restrictedapi” utilice la estrategia “Bearer” de Passport para protegerlo. En nuestro caso, ejecutará el bloque de código del apartado configuración, permitiendo o no el acceso al recurso en función de si el token es correcto.
En mi próxima entrada, explicaré como generar los tokens JWT y registrarse en la aplicación mediante Google.