Saltar al contenidoPedro Farbo
Lección 12 / 2560 min

Autenticación con JWT

Autenticación JWT

Implementaremos autenticación con JSON Web Tokens.

Instalación

bash
npm install jsonwebtoken bcryptjsnpm install -D @types/jsonwebtoken @types/bcryptjs

Configuració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! 🚀

¿Te gustó el contenido? ¡Tu contribución ayuda a mantener todo online y gratuito!

PIX:0737160d-e98f-4a65-8392-5dba70e7ff3e