Autenticación JWT
Implementaremos autenticación con JSON Web Tokens.
Instalación
bash
npm install jsonwebtoken bcryptjsnpm install -D @types/jsonwebtoken @types/bcryptjsConfiguración
typescript
// src/config/auth.tsexport const authConfig = { secret: process.env.JWT_SECRET || 'secret-dev', expiresIn: '7d', refreshExpiresIn: '30d',};Auth Service
typescript
// src/services/auth.service.tsimport bcrypt from 'bcryptjs';import jwt from 'jsonwebtoken';import { prisma } from '../config/database';import { authConfig } from '../config/auth';import { AppError } from '../errors/AppError'; interface RegisterDTO { name: string; email: string; password: string;} export class AuthService { async register(data: RegisterDTO) { const exists = await prisma.user.findUnique({ where: { email: data.email }, }); if (exists) { throw new AppError('Email ya registrado', 409); } const hashedPassword = await bcrypt.hash(data.password, 10); const user = await prisma.user.create({ data: { ...data, password: hashedPassword, }, select: { id: true, name: true, email: true, role: true }, }); const token = this.generateToken(user.id); return { user, token }; } async login(email: string, password: string) { const user = await prisma.user.findUnique({ where: { email }, }); if (!user) { throw new AppError('Credenciales inválidas', 401); } const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) { throw new AppError('Credenciales inválidas', 401); } const token = this.generateToken(user.id); return { user: { id: user.id, name: user.name, email: user.email, role: user.role }, token, }; } private generateToken(userId: string): string { return jwt.sign({ sub: userId }, authConfig.secret, { expiresIn: authConfig.expiresIn, }); }}Auth Middleware
typescript
// src/middlewares/auth.middleware.tsimport { Request, Response, NextFunction } from 'express';import jwt from 'jsonwebtoken';import { authConfig } from '../config/auth';import { prisma } from '../config/database';import { AppError } from '../errors/AppError'; interface TokenPayload { sub: string;} export const authenticate = async ( req: Request, res: Response, next: NextFunction) => { const header = req.headers.authorization; if (!header) { throw new AppError('Token no proporcionado', 401); } const [, token] = header.split(' '); try { const payload = jwt.verify(token, authConfig.secret) as TokenPayload; const user = await prisma.user.findUnique({ where: { id: payload.sub }, select: { id: true, name: true, email: true, role: true }, }); if (!user) { throw new AppError('Usuario no encontrado', 401); } req.user = user; next(); } catch { throw new AppError('Token inválido', 401); }};Extendiendo Request
typescript
// src/@types/express.d.tsdeclare namespace Express { interface Request { user?: { id: string; name: string; email: string; role: 'USER' | 'ADMIN'; }; }}Rutas de Auth
typescript
// src/routes/auth.routes.tsconst router = Router();const controller = new AuthController(); router.post('/register', validate(registerSchema), controller.register);router.post('/login', validate(loginSchema), controller.login); // Ruta protegidarouter.get('/me', authenticate, controller.me);Uso de Ruta Protegida
typescript
// Controlador usando usuario autenticadoasync me(req: Request, res: Response) { const user = await prisma.user.findUnique({ where: { id: req.user!.id }, select: { id: true, name: true, email: true, role: true }, }); return res.json(user);}Resumen
- ✅ Registro con hash de contraseña
- ✅ Login con JWT
- ✅ Middleware de autenticación
- ✅ Rutas protegidas
Próxima clase: Autorización RBAC! 🚀