Menu

JWT no Node.js: gerar, validar e proteger rotas no Express

Um JWT é um token assinado de três partes em base64url que o servidor emite no login e confere a cada requisição. No Node.js você assina e verifica um com o pacote jose ou jsonwebtoken e uma chave secreta.

Esta página tem editores executáveis. Edite, execute e veja a saída na hora.

Um JWT (JSON Web Token) é uma string de três partes em base64url, header.payload.signature, que o servidor entrega ao usuário no login e confere em toda requisição seguinte. A assinatura prova que o servidor emitiu o token e que ninguém o alterou, mas o payload só está codificado, não criptografado, então qualquer um consegue ler. No Node.js você cria um com new SignJWT(payload).sign(secret) do pacote jose e confere com jwtVerify(token, secret).

Ele imprime 3 parts, depois user-42 admin: o jwtVerify conferiu a assinatura e a expiração e retornou o payload. Mude um caractere do segredo passado para o jwtVerify e rode de novo: a verificação falha com signature verification failed.

O que tem dentro de um JWT

O header e o payload são JSON, codificados em base64url. Decodificá-los não exige chave nenhuma, e é por isso que um JWT nunca deve guardar uma senha ou qualquer outra coisa secreta.

As duas primeiras linhas imprimem {"alg":"HS256"} e {"name":"Ada","role":"editor"}, legíveis para qualquer um que tenha o token. A assinatura (43 caracteres no HS256) é o que impede que alguém os edite: troque editor por admin e a assinatura deixa de bater.

ParteContémÉ secreta?
HeaderO algoritmo, como HS256Não, legível
PayloadClaims: sub (id do usuário), exp (expiração), iat (emitido em), seus próprios camposNão, legível
SignatureAssinatura HMAC ou RSA das duas primeiras partesPrecisa da chave para ser criada

Expiração e verificação que falha

setExpirationTime('15m') escreve uma claim exp, e o jwtVerify rejeita um token que passou dela. Toda rejeição é um erro com um code, então você consegue diferenciar um token expirado de um falsificado.

A primeira verificação falha com ERR_JWT_EXPIRED e a segunda, cuja assinatura foi alterada, com ERR_JWS_SIGNATURE_VERIFICATION_FAILED. Para a sua API as duas significam o mesmo (responder 401), mas registrar o código ajuda na hora de depurar.

Proteger uma rota Express com Bearer token

O cliente faz login uma vez, guarda o token e o envia no header Authorization como Bearer <token>. Um middleware verifica o token e coloca o payload em req.user para a rota.

A primeira requisição, GET /me sem token, recebe 401 e Missing token. Envie POST /login com o body {"username": "ada", "password": "lovelace"}, copie o token da resposta e envie GET /me de novo com o header Authorization: Bearer <token>: a resposta é {"id":"user-1","name":"ada"}.

O pacote jsonwebtoken

O jsonwebtoken (npm install jsonwebtoken) é a outra escolha comum. O sign e o verify dele são síncronos (com callbacks opcionais) em vez de retornar promises:

const jwt = require('jsonwebtoken');

const token = jwt.sign({ role: 'admin' }, process.env.JWT_SECRET, {
    subject: 'user-42',
    expiresIn: '1h',
});

const payload = jwt.verify(token, process.env.JWT_SECRET); // throws if invalid

Use jose em código novo: ele não tem dependências e também roda em edge runtimes e navegadores. Tokens das duas bibliotecas são JWTs padrão, então uma consegue verificar o que a outra assinou.

Erros comuns

Usar decode em vez de verify. jwt.decode e decodeJwt leem o payload sem conferir a assinatura, então um token falsificado passa. Só confie no que o verify retorna.

Um segredo curto ou commitado. Um segredo HS256 fácil de adivinhar deixa atacantes assinarem os próprios tokens. Use pelo menos 32 bytes aleatórios, leia de process.env e nunca faça commit dele.

Tokens que nunca expiram. Sem exp, um token vazado funciona para sempre. Sempre defina uma expiração.

Perguntas frequentes

Onde o cliente deve guardar um JWT?

Em um app de navegador, um cookie HttpOnly e Secure é o mais seguro, porque os scripts da página não conseguem lê-lo. localStorage é mais simples, mas qualquer script na página, incluindo um injetado, consegue ler o token. Apps mobile usam o armazenamento seguro da plataforma.

Como faço logout de um usuário com JWT?

O servidor não consegue cancelar um token que já assinou; ele continua válido até expirar. Apague o token no cliente, mantenha os access tokens com vida curta e, se precisar de logout instantâneo, mantenha no servidor uma lista de ids de tokens revogados (jti) e confira a cada requisição.

HS256 ou RS256?

O HS256 usa um único segredo compartilhado para assinar e verificar, o que serve para um backend só. O RS256 assina com uma chave privada e verifica com uma chave pública, então outros serviços conseguem conferir tokens sem poder criá-los.

Por quanto tempo um JWT deve valer?

Access tokens normalmente valem de 5 a 60 minutos. Para sessões mais longas, combine com um refresh token guardado com mais cuidado, que o cliente troca por um access token novo.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

Começar