Autenticação com JWT no Node.js
1. Fundamentos do JWT
JWT (JSON Web Token) é um padrão aberto (RFC 7519) que define uma forma compacta e autossuficiente de transmitir informações entre partes como um objeto JSON. Um token JWT é composto por três partes separadas por pontos:
- Header: contém o tipo do token e o algoritmo de assinatura (ex:
{"alg": "HS256", "typ": "JWT"}) - Payload: contém as claims (declarações) como dados do usuário e metadados (expiração, emissor)
- Signature: assinatura gerada a partir do header, payload e uma chave secreta
Diferente de sessões tradicionais (que exigem armazenamento no servidor), o JWT permite autenticação stateless: o servidor não precisa manter estado da sessão, pois toda informação necessária está contida no token. Tokens de acesso (access tokens) têm curta duração (ex: 15 minutos), enquanto refresh tokens (mais longos) permitem renovar o access token sem exigir novo login.
2. Configuração do Ambiente Node.js
Crie um novo projeto e instale as dependências:
npm init -y
npm install express jsonwebtoken bcryptjs dotenv
npm install prisma @prisma/client --save
Estrutura de pastas sugerida:
src/
├── controllers/
│ └── authController.js
├── middleware/
│ └── authMiddleware.js
├── models/
│ └── User.js
├── routes/
│ └── authRoutes.js
└── server.js
Crie um arquivo .env na raiz:
JWT_SECRET=seu_segredo_super_seguro_aqui
JWT_EXPIRES_IN=15m
REFRESH_TOKEN_SECRET=outro_segredo_para_refresh
3. Criação do Modelo de Usuário e Banco de Dados
Usando Prisma (ORM moderno), defina o schema em prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite" // ou postgresql
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
password String
createdAt DateTime @default(now())
}
Execute npx prisma migrate dev --name init para criar o banco.
Função para hash de senha no registro:
const bcrypt = require('bcryptjs');
async function hashPassword(password) {
const salt = await bcrypt.genSalt(12);
return bcrypt.hash(password, salt);
}
4. Implementação do Registro e Login
Controller de autenticação (src/controllers/authController.js):
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
exports.register = async (req, res) => {
try {
const { email, password, name } = req.body;
// Validações básicas
if (!email || !password || !name) {
return res.status(400).json({ error: 'Todos os campos são obrigatórios' });
}
// Verificar se email já existe
const userExists = await prisma.user.findUnique({ where: { email } });
if (userExists) {
return res.status(409).json({ error: 'Email já cadastrado' });
}
// Hash da senha
const hashedPassword = await bcrypt.hash(password, 12);
// Criar usuário
const user = await prisma.user.create({
data: { email, password: hashedPassword, name }
});
// Gerar token JWT
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN }
);
res.status(201).json({ token, user: { id: user.id, email: user.email, name: user.name } });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Erro interno do servidor' });
}
};
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
// Buscar usuário
const user = await prisma.user.findUnique({ where: { email } });
if (!user) {
return res.status(401).json({ error: 'Credenciais inválidas' });
}
// Verificar senha
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ error: 'Credenciais inválidas' });
}
// Gerar token
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN }
);
res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Erro interno do servidor' });
}
};
Rotas (src/routes/authRoutes.js):
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
router.post('/register', authController.register);
router.post('/login', authController.login);
module.exports = router;
5. Middleware de Autenticação
Crie src/middleware/authMiddleware.js:
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: 'Token não fornecido' });
}
const parts = authHeader.split(' ');
if (parts.length !== 2 || parts[0] !== 'Bearer') {
return res.status(401).json({ error: 'Formato de token inválido' });
}
const token = parts[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // { userId, email, iat, exp }
return next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expirado' });
}
return res.status(401).json({ error: 'Token inválido' });
}
};
6. Rotas Protegidas e Controle de Acesso
Exemplo de rota protegida para perfil do usuário:
// src/routes/userRoutes.js
const express = require('express');
const router = express.Router();
const authMiddleware = require('../middleware/authMiddleware');
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
router.get('/profile', authMiddleware, async (req, res) => {
try {
const user = await prisma.user.findUnique({
where: { id: req.user.userId },
select: { id: true, email: true, name: true, createdAt: true }
});
if (!user) {
return res.status(404).json({ error: 'Usuário não encontrado' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: 'Erro ao buscar perfil' });
}
});
module.exports = router;
No server.js, aplique o middleware em grupos de rotas:
const express = require('express');
const app = express();
app.use(express.json());
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/users', require('./routes/userRoutes')); // todas protegidas pelo middleware na rota
app.listen(3000, () => console.log('Servidor rodando na porta 3000'));
7. Refresh Token e Segurança Adicional
Implemente refresh tokens armazenados em httpOnly cookies:
// No authController.js
exports.refreshToken = async (req, res) => {
const refreshToken = req.cookies?.refreshToken;
if (!refreshToken) {
return res.status(401).json({ error: 'Refresh token não encontrado' });
}
try {
// Verificar refresh token (deve ter secret diferente)
const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
// Gerar novo access token
const newAccessToken = jwt.sign(
{ userId: decoded.userId, email: decoded.email },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
res.json({ token: newAccessToken });
} catch (error) {
return res.status(401).json({ error: 'Refresh token inválido ou expirado' });
}
};
Boas práticas de segurança:
- Use access tokens com expiração curta (15-30 minutos)
- Armazene refresh tokens em httpOnly cookies (não acessíveis via JavaScript)
- Implemente blacklist de tokens revogados (ex: em Redis ou banco de dados)
- Sempre use HTTPS em produção
8. Integração com React (Frontend)
Crie um contexto de autenticação no React:
// AuthContext.jsx
import { createContext, useState, useEffect } from 'react';
import axios from 'axios';
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [token, setToken] = useState(localStorage.getItem('token'));
useEffect(() => {
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
}, [token]);
const login = async (email, password) => {
const response = await axios.post('/api/auth/login', { email, password });
const { token, user } = response.data;
localStorage.setItem('token', token);
setToken(token);
setUser(user);
};
const logout = () => {
localStorage.removeItem('token');
setToken(null);
setUser(null);
delete axios.defaults.headers.common['Authorization'];
};
return (
<AuthContext.Provider value={{ user, token, login, logout }}>
{children}
</AuthContext.Provider>
);
};
Configure interceptors do Axios para renovar token automaticamente:
// axiosInstance.js
import axios from 'axios';
const api = axios.create({ baseURL: 'http://localhost:3000/api' });
api.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const { data } = await axios.post('/api/auth/refresh-token', {}, { withCredentials: true });
localStorage.setItem('token', data.token);
originalRequest.headers['Authorization'] = `Bearer ${data.token}`;
return api(originalRequest);
} catch (refreshError) {
// Redirecionar para login
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
Proteção de rotas no frontend:
// ProtectedRoute.jsx
import { useContext } from 'react';
import { Navigate } from 'react-router-dom';
import { AuthContext } from './AuthContext';
const ProtectedRoute = ({ children }) => {
const { token } = useContext(AuthContext);
return token ? children : <Navigate to="/login" />;
};
Conclusão
Implementar autenticação com JWT no Node.js oferece uma solução escalável e stateless para aplicações modernas. Combinando tokens de acesso de curta duração com refresh tokens seguros, você obtém um sistema robusto. A integração com React através de contextos e interceptors do Axios completa o ecossistema, proporcionando uma experiência de usuário fluida e segura.
Referências
- Documentação oficial do jsonwebtoken — Biblioteca padrão para criação e verificação de JWTs no Node.js
- JWT.io - JSON Web Tokens Introduction — Visão geral completa sobre estrutura e funcionamento do JWT
- Prisma Documentation - Authentication Example — Guia oficial do Prisma com exemplos de autenticação JWT
- MDN Web Docs - HTTP Cookies — Artigo completo sobre cookies, incluindo httpOnly e Secure flags
- React Authentication with JWT - Robin Wieruch — Tutorial prático de autenticação JWT em React com context e protected routes
- OWASP - JSON Web Token Cheat Sheet — Boas práticas de segurança para implementação de JWTs (aplicável a Node.js)
- Axios Interceptors Documentation — Documentação oficial sobre interceptors para requisições e respostas