Como validar o JWT utilizando um middleware no ExpressJS

Iniciando o projeto

mkdir jwt-middleware
cd jwt-middleware
npm i express jsonwebtoken body-parser
touch index.js

Dentro do arquivo index.js vamos adicionar o básico para ver a aplicação em execução:

const express = require('express');
const app = express();

// Efetua o parse do application/json
const bodyParser = require('body-parser');
app.use(bodyParser.json());

app.get(
    "/status",
    (req, res) => res.json({ status: "OK" })
);

app.listen(3000, () => {
  console.log("Aplicacao em execucao");
});

Executando e testando o endpoint /status:

# Executando a aplicação
node index.js

# Testando o GET /status:
curl http://localhost:3000/status

# Resultado:
{"status":"OK"}

Autenticando o usuário e criando o JWT

Vamos criar uma nova rota, POST /login, para validar as credenciais de um usuário e gerar um JWT assinado com nossa chavePrivada:

// index.js
// ... (código ocultado) ...

app.post(
    "/login",
    (req, res) => {
        // ************************************
        // Observação: o comando abaixo
        // const { usuario, senha } = req.body;
        // é a mesma coisa que digitar:
        // const usuario = req.body.usuario;
        // const senha = req.body.senha;
        // ************************************
        const { usuario, senha } = req.body;

        if (usuario === "marcelo" && senha === "123456") {
            const jwt = require("jsonwebtoken");
            const dadosUsuario = {
                nome: "marcelo",
                email: "teste@gmail.com",
                id: 1
            };
            
            const chavePrivada = "consolelog.com.br";

            jwt.sign(dadosUsuario, chavePrivada, (err, token) => {
                if (err) {
                    res
                        .status(500)
                        .json({ mensagem: "Erro ao gerar o JWT" });

                    return;
                }

                res.set("x-access-token", token);
                res.end();
            });
        } else {
            res.status(401);
            res.end();
        }
    }
);

// ... (código ocultado) ...

Executando os testes:

# Usuário e senha corretos:
curl -i
     -X POST http://localhost:3000/login
     --header "content-type: application/json"
     -d '{ "usuario": "marcelo", "senha": "123456" }'

HTTP/1.1 200 OK
X-Powered-By: Express
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub21lIjoibWFyY2VsbyIsImVtYWlsIjoidGVzdGVAZ21haWwuY29tIiwiaWQiOjEsImlhdCI6MTYxNDY4ODg0NH0._FjjvgFFNPLYMp2Oi_1nK5yaRI3zslKag83SLgxx7_U

# Senha inválida:
curl -i
     -X POST http://localhost:3000/login
     --header "content-type: application/json"
     -d '{ "usuario": "marcelo", "senha": "123457" }'

HTTP/1.1 401 Unauthorized
X-Powered-By: Express

Validando o JWT (Json Web Token)

Agora vamos criar um outro endpoint, GET /user, para obter as informações do usuário logado:

// index.js
// ... (código ocultado) ...

app.get(
    "/user",
    (req, res) => {
        const jwt = req.headers["authorization"];
        const chavePrivada = "consolelog.com.br";

        // Efetuando a validação do JWT:
        const jwtService = require("jsonwebtoken");
        jwtService.verify(jwt, chavePrivada, (err, userInfo) => {
            if (err) {
                res.status(403).end();
                return;
            }

            res.json(userInfo);
        });
    }
);

// ... (código ocultado) ...

Resultado:

# JWT válido gerado na etapa anterior:
curl -i http://localhost:3000/user
     --header "authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub21lIjoibWFyY2VsbyIsImVtYWlsIjoidGVzdGVAZ21haWwuY29tIiwiaWQiOjEsImlhdCI6MTYxNDY4ODg0NH0._FjjvgFFNPLYMp2Oi_1nK5yaRI3zslKag83SLgxx7_U"

# Resultado
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 68

{"nome":"marcelo","email":"teste@gmail.com","id":1,"iat":1614688844}

# JWT inválido
curl -i http://localhost:3000/user
     --header "authorization: 123"

# Resultado
HTTP/1.1 403 Forbidden
X-Powered-By: Express

Utilizando um middleware

Repare que temos os seguintes endpoints:

  • GET /status - não precisa de autenticação
  • POST /login - efetua a autenticação de um usuário gerando um JWT
  • GET /user - retorna as informações do usuário caso o JWT seja válido

Conforme sua aplicação cresce vamos ter novas rotas do tipo:

  • POST /perfis - cadastra um perfil no sistema
  • GET /fotos - retorna as fotos do usuário

Cada nova rota que exigir a identificação do usuário será necessário validar o JWT e depois obter as informações como o id do usuário por exemplo. Para não repetir a lógica criada na rota GET /user, vamos criar um middleware e vinculá-lo apenas às rotas que desejarmos. Para fazer esta alteração devemos alterar o arquivo index.js para criar o middleware:

// index.js

const express = require('express');
const app = express();
const bodyParser = require('body-parser');

const middlewareValidarJWT = (req, res, next) => {
    const jwt = req.headers["authorization"];
    const chavePrivada = "consolelog.com.br";

    // Efetuando a validação do JWT:
    const jwtService = require("jsonwebtoken");
    jwtService.verify(jwt, chavePrivada, (err, userInfo) => {
        if (err) {
            res.status(403).end();
            return;
        }

        // O objeto "req" é alterado abaixo
        // recebendo uma nova propriedade userInfo.
        // Este mesmo objeto chegará na rota
        // podendo acessar o req.userInfo
        req.userInfo = userInfo;
        next();
    });
};

// ... (código ocultado) ...

app.get(
    "/user",
    middlewareValidarJWT, // <<< INCLUSÃO DO MIDDLEWARE
    (req, res) => {
        res.json(req.userInfo);
    }
);

// ... (código ocultado) ...

A alteração acima não mudou o funcionamento do código. Essa refatoração permitiu isolar a lógica de validação do JWT em um middleware que agora pode ser reaproveitada em outras rotas. Para testar este reaproveitamento, vamos criar uma nova rota conforme o código abaixo:

// ... (código ocultado) ...

app.get(
    "/fotos",
    middlewareValidarJWT,
    (req, res) => {
        const { id } = req.userInfo;
        res.json({ dados: `Fotos do usuário de id: ${id}` });
    }
);

// ... (código ocultado) ...

Ao executar a aplicação e testar este novo endpoint, GET /fotos,  obtemos o seguinte resultado:

# Requisição com JWT válido
curl -i http://localhost:3000/fotos
     --header "authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub21lIjoibWFyY2VsbyIsImVtYWlsIjoidGVzdGVAZ21haWwuY29tIiwiaWQiOjEsImlhdCI6MTYxNDY4ODg0NH0._FjjvgFFNPLYMp2Oi_1nK5yaRI3zslKag83SLgxx7_U"
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8

{"dados":"Fotos do usuário de id: 1"}
# Com um JWT inválido:
curl -i http://localhost:3000/fotos
     --header "authorization: 123"
HTTP/1.1 403 Forbidden
X-Powered-By: Express

Código completo:

const express = require('express');
const app = express();

const middlewareValidarJWT = (req, res, next) => {
    const jwt = req.headers["authorization"];
    const chavePrivada = "consolelog.com.br";

    // Efetuando a validação do JWT:
    const jwtService = require("jsonwebtoken");
    jwtService.verify(jwt, chavePrivada, (err, userInfo) => {
        if (err) {
            res.status(403).end();
            return;
        }

        req.userInfo = userInfo;
        next();
    });
};

// Efetua o parse do application/json
const bodyParser = require('body-parser');
app.use(bodyParser.json());

app.get(
    "/status",
    (req, res) => {
        const jwt = req.headers['authorization'];

        res.json({
            status: "OK",
            jwt
        });
    }
);

app.post(
    "/login",
    (req, res) => {
        const { usuario, senha } = req.body;

        if (usuario === "marcelo" && senha === "123456") {
            const jwt = require("jsonwebtoken");
            const dadosUsuario = {
                nome: "marcelo",
                email: "teste@gmail.com",
                id: 1
            };

            const chavePrivada = "consolelog.com.br";

            jwt.sign(dadosUsuario, chavePrivada, (err, token) => {
                if (err) {
                    res
                        .status(500)
                        .json({ mensagem: "Erro ao gerar o JWT" });

                    return;
                }

                res.set("x-access-token", token);
                res.end();
            });
        } else {
            res.status(401);
	    res.end();
        }
    }
);

app.get(
    "/user",
    middlewareValidarJWT,
    (req, res) => {
        res.json(req.userInfo);
    }
);

app.get(
    "/fotos",
    middlewareValidarJWT,
    (req, res) => {
        const { id } = req.userInfo;
        res.json({ dados: `Fotos do usuário de id: ${id}` });
    }
);

app.listen(3000, () => {
  console.log("Aplicacao em execucao");
});

Links interessantes: