Middlewares de autenticação e autorização
1. Conceitos Fundamentais de Autenticação e Autorização
Antes de implementar middlewares, é crucial entender a diferença entre autenticação e autorização. Autenticação responde "quem é você?" — verifica a identidade do usuário através de credenciais como email/senha ou tokens. Autorização responde "o que você pode fazer?" — determina quais recursos ou ações o usuário autenticado tem permissão para acessar.
O JWT (JSON Web Token) é o padrão mais utilizado para autenticação stateless. Sua estrutura é composta por três partes separadas por pontos:
- Header: contém o tipo do token e o algoritmo de hash (ex:
HS256) - Payload: contém as claims (dados do usuário, expiração, etc.)
- Signature: hash criptográfico que garante integridade
O fluxo típico é: login → servidor valida credenciais → gera JWT → cliente armazena token → envia em requisições protegidas via header Authorization.
2. Implementando um Middleware de Autenticação com JWT
Vamos criar um middleware que extrai e valida o token JWT do header Authorization (formato Bearer):
const jwt = require('jsonwebtoken');
const authenticate = (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; // Injeta dados do usuário na requisição
return next();
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expirado' });
}
return res.status(401).json({ error: 'Token inválido' });
}
};
module.exports = authenticate;
3. Middleware de Autorização por Papéis (Roles)
Agora implementamos um middleware que verifica se o usuário possui a role necessária. Usaremos uma factory function que aceita as roles permitidas:
const authorize = (...allowedRoles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Usuário não autenticado' });
}
const { role } = req.user;
if (!allowedRoles.includes(role)) {
return res.status(403).json({
error: 'Acesso negado. Permissão insuficiente'
});
}
return next();
};
};
module.exports = authorize;
Exemplo de uso em rotas:
const express = require('express');
const router = express.Router();
const authenticate = require('./middlewares/authenticate');
const authorize = require('./middlewares/authorize');
// Rota protegida para qualquer usuário autenticado
router.get('/profile', authenticate, (req, res) => {
res.json({ user: req.user });
});
// Rota exclusiva para administradores
router.delete('/users/:id', authenticate, authorize('admin'), async (req, res) => {
// Lógica de exclusão
});
// Rota para admin ou moderator
router.put('/posts/:id', authenticate, authorize('admin', 'moderator'), async (req, res) => {
// Lógica de atualização
});
4. Gerenciamento de Sessões e Refresh Tokens
Tokens JWT precisam de curta duração (15-30 minutos) para segurança. Implementamos refresh tokens para renovação automática:
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
// Armazenamento em memória (substituir por Redis em produção)
const refreshTokens = new Map();
const generateTokens = (user) => {
const accessToken = jwt.sign(
{ id: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || '15m' }
);
const refreshToken = crypto.randomBytes(40).toString('hex');
refreshTokens.set(refreshToken, {
userId: user.id,
expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 dias
});
return { accessToken, refreshToken };
};
const refreshTokenMiddleware = async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: 'Refresh token não fornecido' });
}
const storedToken = refreshTokens.get(refreshToken);
if (!storedToken || storedToken.expiresAt < Date.now()) {
refreshTokens.delete(refreshToken);
return res.status(401).json({ error: 'Refresh token inválido ou expirado' });
}
// Rotação de token: remove o antigo e gera novo
refreshTokens.delete(refreshToken);
const user = await User.findById(storedToken.userId);
if (!user) {
return res.status(401).json({ error: 'Usuário não encontrado' });
}
const tokens = generateTokens(user);
res.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000
});
return res.json({ accessToken: tokens.accessToken });
};
5. Proteção de Rotas no Frontend com React
No lado do React, criamos um sistema de autenticação com Context API e componentes protegidos:
// AuthContext.js
import React, { createContext, useContext, useState, useEffect } from 'react';
import api from './api';
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const token = localStorage.getItem('accessToken');
if (token) {
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// Verificar token atual
api.get('/auth/me')
.then(response => setUser(response.data))
.catch(() => logout())
.finally(() => setLoading(false));
} else {
setLoading(false);
}
}, []);
const login = async (email, password) => {
const response = await api.post('/auth/login', { email, password });
const { accessToken, refreshToken } = response.data;
localStorage.setItem('accessToken', accessToken);
api.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
setUser(response.data.user);
};
const logout = () => {
localStorage.removeItem('accessToken');
delete api.defaults.headers.common['Authorization'];
setUser(null);
};
return (
<AuthContext.Provider value={{ user, loading, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth deve ser usado dentro de AuthProvider');
}
return context;
};
// PrivateRoute.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { useAuth } from './AuthContext';
const PrivateRoute = ({ component: Component, roles, ...rest }) => {
const { user, loading } = useAuth();
if (loading) {
return <div>Carregando...</div>;
}
return (
<Route
{...rest}
render={props => {
if (!user) {
return <Redirect to="/login" />;
}
if (roles && !roles.includes(user.role)) {
return <Redirect to="/unauthorized" />;
}
return <Component {...props} />;
}}
/>
);
};
export default PrivateRoute;
// Configuração do Axios com interceptors para refresh automático
import axios from 'axios';
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL
});
api.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refreshToken');
const response = await api.post('/auth/refresh', { refreshToken });
const { accessToken } = response.data;
localStorage.setItem('accessToken', accessToken);
api.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
return api(originalRequest);
} catch (refreshError) {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
export default api;
6. Boas Práticas e Segurança
Para garantir a segurança da sua implementação:
// Configuração segura
require('dotenv').config();
// Validação de entrada no middleware
const validateLoginInput = (req, res, next) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email e senha são obrigatórios' });
}
// Sanitização básica
req.body.email = email.trim().toLowerCase();
next();
};
// Rate limiting para prevenir brute force
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 5, // 5 tentativas
message: { error: 'Muitas tentativas de login. Tente novamente mais tarde' }
});
// Blacklist de tokens (exemplo com Redis)
const redis = require('redis');
const client = redis.createClient();
const logout = async (req, res) => {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.decode(token);
// Adiciona token à blacklist até expirar
await client.set(`blacklist:${token}`, 'true', 'EX', decoded.exp - Math.floor(Date.now() / 1000));
res.json({ message: 'Logout realizado com sucesso' });
};
Resumo das boas práticas:
- Armazene JWT_SECRET em variáveis de ambiente
- Use httpOnly cookies para refresh tokens
- Implemente rate limiting em rotas de login
- Faça validação e sanitização de todos os inputs
- Use blacklist de tokens para logout efetivo
- Mantenha access tokens com curta duração (15-30 min)
- Rode refresh tokens a cada uso
Referências
- Documentação oficial do jsonwebtoken — Biblioteca padrão para criação e verificação de JWT em Node.js
- JWT.io - JSON Web Tokens Introduction — Explicação completa sobre estrutura e funcionamento do JWT
- MDN Web Docs - HTTP Authorization header — Documentação sobre o esquema de autenticação Bearer
- React Context API - Documentação Oficial — Guia oficial para gerenciamento de estado global com Context
- OWASP - Authentication Cheat Sheet — Melhores práticas de segurança para implementação de autenticação
- Express.js - Writing Middleware — Guia oficial sobre criação de middlewares no Express
- Auth0 - Refresh Token Rotation — Estratégia de rotação de refresh tokens para maior segurança