Autenticação com JWT
Autenticação é fundamental para proteger sua API. Vamos implementar um sistema completo com JWT (JSON Web Tokens).
Instalando Dependências
bash
npm install jsonwebtoken bcryptnpm install -D @types/jsonwebtoken @types/bcryptComo JWT Funciona
1. Cliente envia credenciais (email/senha)
2. Servidor valida e gera token JWT
3. Cliente armazena token e envia em cada requisição
4. Servidor valida token e autoriza acesso
┌─────────────┐ POST /login ┌─────────────┐
│ Cliente │ ──────────────────▶ │ Servidor │
└─────────────┘ email + senha └─────────────┘
│
│ Valida
▼
┌─────────────┐ JWT Token ┌─────────────┐
│ Cliente │ ◀────────────────── │ Servidor │
└─────────────┘ └─────────────┘
│
│ Armazena token
│
▼
┌─────────────┐ GET /api/profile ┌─────────────┐
│ Cliente │ ──────────────────▶ │ Servidor │
└─────────────┘ Authorization: └─────────────┘
Bearer <token>
Configuração JWT
typescript
// src/config/auth.tsexport const authConfig = { jwt: { secret: process.env.JWT_SECRET || 'default-secret-change-me', expiresIn: process.env.JWT_EXPIRES_IN || '7d', refreshExpiresIn: '30d', }, bcrypt: { saltRounds: 10, },};Service de Autenticação
typescript
// src/services/auth.service.tsimport { hash, compare } from 'bcrypt';import jwt from 'jsonwebtoken';import { prisma } from '../lib/prisma';import { authConfig } from '../config/auth';import { UnauthorizedError, ConflictError } from '../errors'; interface TokenPayload { userId: string; email: string; role: string;} interface AuthResponse { user: { id: string; email: string; name: string; role: string }; token: string; refreshToken: string;} export class AuthService { async register(data: { name: string; email: string; password: string; }): Promise<AuthResponse> { // Verificar se email existe const existingUser = await prisma.user.findUnique({ where: { email: data.email }, }); if (existingUser) { throw new ConflictError('Email já cadastrado'); } // Hash da senha const hashedPassword = await hash(data.password, authConfig.bcrypt.saltRounds); // Criar usuário const user = await prisma.user.create({ data: { name: data.name, email: data.email, password: hashedPassword, }, }); // Gerar tokens const tokens = this.generateTokens({ userId: user.id, email: user.email, role: user.role, }); return { user: { id: user.id, email: user.email, name: user.name, role: user.role, }, ...tokens, }; } async login(email: string, password: string): Promise<AuthResponse> { // Buscar usuário const user = await prisma.user.findUnique({ where: { email }, }); if (!user) { throw new UnauthorizedError('Credenciais inválidas'); } // Verificar senha const validPassword = await compare(password, user.password); if (!validPassword) { throw new UnauthorizedError('Credenciais inválidas'); } // Gerar tokens const tokens = this.generateTokens({ userId: user.id, email: user.email, role: user.role, }); return { user: { id: user.id, email: user.email, name: user.name, role: user.role, }, ...tokens, }; } async refreshToken(refreshToken: string): Promise<{ token: string }> { try { const payload = jwt.verify( refreshToken, authConfig.jwt.secret ) as TokenPayload; const token = jwt.sign( { userId: payload.userId, email: payload.email, role: payload.role }, authConfig.jwt.secret, { expiresIn: authConfig.jwt.expiresIn } ); return { token }; } catch { throw new UnauthorizedError('Refresh token inválido'); } } private generateTokens(payload: TokenPayload) { const token = jwt.sign(payload, authConfig.jwt.secret, { expiresIn: authConfig.jwt.expiresIn, }); const refreshToken = jwt.sign(payload, authConfig.jwt.secret, { expiresIn: authConfig.jwt.refreshExpiresIn, }); return { token, refreshToken }; } verifyToken(token: string): TokenPayload { return jwt.verify(token, authConfig.jwt.secret) as TokenPayload; }}Middleware de Autenticação
typescript
// src/middlewares/auth.middleware.tsimport { Request, Response, NextFunction } from 'express';import { AuthService } from '../services/auth.service';import { UnauthorizedError } from '../errors'; // Extender tipo Requestdeclare global { namespace Express { interface Request { user?: { userId: string; email: string; role: string; }; } }} const authService = new AuthService(); export function authenticate(req: Request, res: Response, next: NextFunction) { const authHeader = req.headers.authorization; if (!authHeader) { throw new UnauthorizedError('Token não fornecido'); } const [type, token] = authHeader.split(' '); if (type !== 'Bearer' || !token) { throw new UnauthorizedError('Formato de token inválido'); } try { const payload = authService.verifyToken(token); req.user = payload; next(); } catch (error) { throw new UnauthorizedError('Token inválido ou expirado'); }} // Middleware opcional - não bloqueia se não tiver tokenexport function optionalAuth(req: Request, res: Response, next: NextFunction) { const authHeader = req.headers.authorization; if (authHeader) { const [type, token] = authHeader.split(' '); if (type === 'Bearer' && token) { try { req.user = authService.verifyToken(token); } catch { // Ignora token inválido } } } next();}Controller e Rotas
typescript
// src/controllers/auth.controller.tsimport { Request, Response } from 'express';import { AuthService } from '../services/auth.service'; const authService = new AuthService(); export class AuthController { async register(req: Request, res: Response) { const result = await authService.register(req.body); res.status(201).json({ success: true, data: result }); } async login(req: Request, res: Response) { const { email, password } = req.body; const result = await authService.login(email, password); res.json({ success: true, data: result }); } async refresh(req: Request, res: Response) { const { refreshToken } = req.body; const result = await authService.refreshToken(refreshToken); res.json({ success: true, data: result }); } async me(req: Request, res: Response) { const user = await prisma.user.findUnique({ where: { id: req.user!.userId }, select: { id: true, email: true, name: true, role: true }, }); res.json({ success: true, data: user }); }}typescript
// src/routes/auth.routes.tsimport { Router } from 'express';import { AuthController } from '../controllers/auth.controller';import { authenticate } from '../middlewares/auth.middleware';import { validate } from '../middlewares/validate.middleware';import { registerSchema, loginSchema } from '../schemas/auth.schema'; const router = Router();const authController = new AuthController(); router.post('/register', validate({ body: registerSchema }), authController.register);router.post('/login', validate({ body: loginSchema }), authController.login);router.post('/refresh', authController.refresh);router.get('/me', authenticate, authController.me); export default router;Protegendo Rotas
typescript
// Rotas públicasapp.use('/api/auth', authRoutes);app.use('/api/products', productRoutes); // Listagem pública // Rotas protegidasapp.use('/api/orders', authenticate, orderRoutes);app.use('/api/profile', authenticate, profileRoutes);Resumo
- ✅ JWT para autenticação stateless
- ✅ Bcrypt para hash de senhas
- ✅ Refresh tokens para renovação
- ✅ Middleware de autenticação
- ✅ Proteção de rotas
Próxima aula: Autorização e RBAC! 👥