JWT Aplicación
En el post de hoy te vamos a estar compartiendo una posible aplicación de los JWT en Node con Express a través de authorization y refresh tokens, muchos de los conceptos de los que hablamos los encontrás en el post anterior: JSON Web Tokens: Una introducción
¡Arranquemos!

Autenticandonos

1) Vamos a crear una ruta que invoque a nuestro servicio de Login, el endpoint será un post con path: /login.
Primero obtenemos los datos del usuario del body de la request, y luego tendremos un servicio que se encargará de chequear que exista nuestro usuario, y que el usuario y contraseña coincidan con el que nos mandaron a través de la request. En caso de ser así, creamos un authToken y un refreshToken y los enviamos por los headers de la response.
router.post('/login', function (req, res) {
    const user = req.body;
    if (checkUser(user)) {
        const token = createAuthToken(user);
        const refreshToken = createRefreshToken(user);
        res
            .status(200)
            .header({ 'auth-token': token })
            .header({ 'refresh-token': refreshToken })
            .json('Logged')
            .send();
    } else {
        res
            .status(400)
            .json('Usuario no encontrado')
            .send();
    }
});
2) Ahora vamos a meternos de lleno en el método createAuthToken.
Primero importamos jwt en nuestro proyecto, en nuestro caso particular utilizaremos la dependencia jsonwebtoken, la podes instalar en tu proyecto Node usando: npm i jsonwebtoken. (Si querés conocer si existe una librería para el lenguaje en el que estas trabajando consulta: JSON Web Token Libraries)

El método createAuthToken: recibe como parámetro el usuario logueado, y genera un token con el id del usuario y su nombre. También recibirá dos parámetros adicionales: por un lado un secret; un string que usaremos para firmar el token, y establecer el tiempo de vida del token. Finalmente devolvemos el token generado por jwt.
const jwt = require('jsonwebtoken');
const TOKEN_SECRET = 'my_Super_Secret';
const AUTH_TOKEN_LIVE_TIME = '300s';

const createAuthToken = (user) => {
    const { id, username } = user;
    const token = jwt.sign({ id, username }, TOKEN_SECRET, { expiresIn: AUTH_TOKEN_LIVE_TIME });
    return token;
}

El método createRefreshToken también recibe como parámetro el usuario logueado, crea un refresh token, despues lo guarda (En nuestro caso lo hacemos en memoria), y por último lo retorna.
const refreshTokens = [];
const createRefreshToken = (user) => {
    const { id, username } = user;
    const refreshToken = jwt.sign({ id, username }, TOKEN_SECRET, { expiresIn: REFRESH_TOKEN_LIVE_TIME });
    refreshTokens[id] = refreshToken;
    return refreshToken;
}

Renovando el token

Cuando el authorizationToken se venza vamos a necesitar un mecanismo para renovarlo, para lo cuál usaremos el resfreshToken.
1) Vamos a crear una ruta ‘/refresh-token’:
router.post('/refresh-token', function (req, res) {
    const refreshToken = req.headers['refresh-token'];
    if (!refreshToken || !checkRefreshToken(refreshToken)) {
        res.status(400).json('No se pudo completar la operación.').send();
    }
    const user = decodeToken(refreshToken);
    const token = createAuthToken(user);
    res
        .status(200)
        .header({ 'auth-token': token })
        .json('Refreshed')
        .send();
});

Notá que esté método primero va a comprobar si existe un refreshToken en los headers de la request y que ese token sea válido, sino existe o es inválido envío un mensaje de error. En caso contrario, primero decodifico el resfreshToken para obtener datos del usuario y generar nuevamente su authorization token.
	const checkRefreshToken = (refreshToken) => {
    try {
        const user = decodeToken(refreshToken);
        const existingRefreshToken = refreshTokens[user.id];
        if (existingRefreshToken) {
            const existingUser = decodeToken(existingRefreshToken);
            return user.id === existingUser.id && user.username === existingUser.username;
        } else {
            throw new Error('El usuario ingresado no tiene un refreshToken existente, vuelva a iniciar sesión');
        }
    } catch (err) {
        throw new Error(err);
    }
}
En este método chequeamos el refreshToken, primero lo decodificamos, y obtenemos datos del usuario. Vamos a usar el id del usuario para buscar el refreshToken que guardamos antes en memoria (Cuando el usuario se logueó), y si existe, primero lo decodificamos, luego chequeamos que coincida con los datos del usuario del refresh enviado por parámetro, si coinciden le damos el OK para generar un nuevo authorizationToken.

Controlando los accesos

Una vez que hayamos implementado la generación de tokens, nos resta controlar el acceso a los endpoints, por lo cuál, cada vez que un cliente invoque a un endpoint vamos a pedirle que nos envíe el authorizationToken por los headers de la request.
Para implementarlo, vamos a construir un middleware que compruebe que tengamos el token en los headers y que sea válido, en tal caso, continuamos el llamado al service para acceder a los recursos protegidos, en caso contrario, le devolvemos un error.
function checkTokenMiddleware(req, res, next) {
    const token = req.header('auth-token');
    try {
        if (token) {
            verifyTokensStatus(token);            
            next();
        } else {
            throw new Error('El token no existe');
        }
    } catch (error) {
        throw new Error(error);
    }
}
Para verificar el estado del token, la implementación de la librería nos brinda el método verify, que recibe como parámetro el token, y el secret:
const verifyTokensStatus = (token) => {
    try {
        return jwt.verify(token, TOKEN_SECRET);
    } catch (err) {
        throw new Error(err);
    }
}
Si se produce un error, lanzamos una excepción. Por último enganchamos el controller que queremos securizar con nuestro middleware:
router.use(checkTokenMiddleware);
router.get('/protected', function (req, res) {
    res.send('Recurso protegido');
});
¡Listo, endpoint securizado!
Ahora cada vez que queramos hacer un get a /protected, vamos a necesitar enviar nuestro authorizationToken.

Mandanos tus sugerencias

Ayudanos con ideas para los artículos de este blog a contacto@somospnt.com

¡Seguínos en nuestras redes sociales para enterarte de los últimos posts!