O que é um JWT
Parte da seção Autenticação e segurança do Journey de Node.js da Coddy. Lição 14 de 24.
Uma sessão precisa consultar o banco de dados em cada solicitação. Um JWT (JSON Web Token) carrega as informações por conta própria: três partes unidas por pontos. Cada parte é escrita em base64url, uma forma de representar bytes com letras, dígitos, - e _. O cabeçalho indica o algoritmo, o payload contém as reivindicações (sub indica de quem é o token, exp indica quando ele expira), e a assinatura comprova que o servidor o criou.
Base64url é uma codificação, não uma criptografia. Qualquer pessoa com um token pode ler o payload:
const payload = token.split('.')[1];
JSON.parse(Buffer.from(payload, 'base64url').toString());
// { sub: '42', name: 'Lin', exp: 1767225600 }decodeJwt(token) de jose, o pacote JWT, faz o mesmo. Ele lê o payload sem verificar a assinatura e gera um erro quando o texto não é um JWT. Portanto, nunca coloque um segredo no payload e nunca tome decisões com base em uma reivindicação fornecida por decodeJwt: qualquer pessoa pode escrever qualquer reivindicação.
Desafio
FácilEste capítulo adiciona uma segunda forma de fazer login, com tokens, para aplicativos que chamam sua API. Ele começa com uma cópia menor do servidor, com cadastro e login, mas sem sessões nem anotações. Um JWT carrega suas informações dentro dele, e qualquer pessoa pode lê-las. Crie uma rota que mostre o que um token diz e feche uma página de administração que confia em tudo o que um token afirma.
- Em
server.js, escrevaGET /inspect. Ela lê um token da string de consulta, como emGET /inspect?token=eyJ..., e retorna as declarações contidas nele como JSON. Leia-as comdecodeJwt, que já foi importado. Para um token cujo conteúdo seja{"sub": "lin", "plan": "free"}, a rota retorna exatamente{"sub": "lin", "plan": "free"}. decodeJwtgera um erro quando o texto não é um JWT. Capture esse erro e responda com o status 400 e{"error": "Not a JWT"}. Por exemplo,GET /inspect?token=hellorecebe esse status 400.- Veja
GET /admin. Ela lê o token comdecodeJwte mostra um segredo quando o token diz"admin": true. MasdecodeJwtnunca verifica a assinatura, então qualquer pessoa pode escrever um token que diga"admin": true. Este servidor ainda não consegue verificar assinaturas. Então façaGET /adminresponder com o status 403 e{"error": "Admins only"}a todas as solicitações, qualquer que seja o token recebido. 403 Forbidden significa "você não pode ver isto". O capítulo 4 abrirá uma página de administração novamente, de verdade, apenas para administradores.
Experimente: Pressione Run. A barra de solicitações começa com GET /admin?token= seguido deste token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYWxsb3J5IiwiYWRtaW4iOnRydWV9.c2lnbmF0dXJl Um invasor chamado mallory o escreveu à mão, sem nenhuma chave secreta. Envie-o antes de alterar qualquer coisa: a versão inicial retorna {"secret": "The vault key is 42"}. Depois da sua alteração, execute novamente e envie a mesma solicitação: status 403 e {"error": "Admins only"}. Envie GET /inspect?token= seguido do mesmo token: {"sub": "mallory", "admin": true}, lido sem nenhuma chave. Envie GET /inspect?token=hello: status 400 e {"error": "Not a JWT"}.
Experimente você mesmo
import express from 'express';
import { decodeJwt } from 'jose';
import { users } from './db.js';
import { hashPassword, verifyPassword } from './passwords.js';
const app = express();
app.use(express.json());
app.post('/signup', async (req, res) => {
const username = String(req.body.username ?? '').trim().toLowerCase();
const password = String(req.body.password ?? '');
if (!username || password.length < 8) {
return res.status(400).json({ error: 'Pick a username and a password of at least 8 characters' });
}
if (await users.findOne({ username })) {
return res.status(409).json({ error: 'That username is taken' });
}
await users.insertOne({ username, passwordHash: await hashPassword(password) });
res.status(201).json({ username });
});
app.post('/login', async (req, res) => {
const username = String(req.body.username ?? '').trim().toLowerCase();
const user = await users.findOne({ username });
if (!user || !(await verifyPassword(String(req.body.password ?? ''), user.passwordHash))) {
return res.status(401).json({ error: 'Wrong username or password' });
}
res.json({ username: user.username });
});
// Mostra o que um token diz: GET /inspect?token=...
app.get('/inspect', (req, res) => {
res.status(501).json({ error: 'Not written yet' });
});
// A página do administrador.
app.get('/admin', (req, res) => {
const claims = decodeJwt(String(req.query.token ?? ''));
if (claims.admin === true) {
return res.json({ secret: 'The vault key is 42' });
}
res.status(403).json({ error: 'Admins only' });
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Autenticação e segurança
Pratique por conta própria: Playground de Express