Saltearse al contenido

Autenticación y Autorización

Aprende a implementar autenticación segura y control de acceso basado en roles (RBAC) en tus aplicaciones Veloce-TS.

Veloce-TS v0.2.6 proporciona un sistema completo de autenticación y autorización:

  • Autenticación basada en JWT con tokens de acceso y actualización
  • Control de Acceso Basado en Roles (RBAC) con roles jerárquicos
  • Autorización basada en Permisos para control de acceso granular
  • Gestión de Usuarios con hash de contraseñas y validación
  • Gestión de Sesiones (opcional)
import { VeloceTS } from 'veloce-ts';
const app = new VeloceTS();
app.install('auth', {
jwt: {
secret: process.env.JWT_SECRET || 'tu-clave-secreta',
accessTokenTTL: 3600, // 1 hora
refreshTokenTTL: 86400, // 24 horas
},
userProvider: {
findByCredentials: async (username: string, password: string) => {
// Buscar usuario por nombre de usuario/email
const user = await userService.findByUsername(username);
if (!user) return null;
// Verificar contraseña
const isValid = await userService.verifyPassword(password, user.password);
return isValid ? user : null;
},
findById: async (id: string) => {
return await userService.findById(id);
},
hashPassword: async (password: string) => {
return await bcrypt.hash(password, 10);
},
verifyPassword: async (password: string, hash: string) => {
return await bcrypt.compare(password, hash);
}
}
});
import {
Controller,
Post,
Get,
Body,
Auth,
CurrentUser
} from 'veloce-ts';
import { z } from 'zod';
const LoginSchema = z.object({
username: z.string().min(1),
password: z.string().min(6)
});
const RegisterSchema = z.object({
username: z.string().min(3).max(50),
email: z.string().email(),
password: z.string().min(6)
});
@Controller('/auth')
export class AuthController {
@Post('/login')
async login(@Body(LoginSchema) credentials: z.infer<typeof LoginSchema>) {
const user = await userService.validateCredentials(
credentials.username,
credentials.password
);
if (!user) {
throw new UnauthorizedException('Credenciales inválidas');
}
const tokens = authService.generateTokens({
sub: user.id,
username: user.username,
email: user.email,
role: user.role,
permissions: user.permissions
});
return {
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role
}
};
}
@Post('/register')
async register(@Body(RegisterSchema) userData: z.infer<typeof RegisterSchema>) {
const existingUser = await userService.findByUsername(userData.username);
if (existingUser) {
throw new ConflictException('El nombre de usuario ya existe');
}
const hashedPassword = await userService.hashPassword(userData.password);
const user = await userService.create({
...userData,
password: hashedPassword,
role: 'viewer', // Rol por defecto
permissions: ['tasks.read'] // Permisos por defecto
});
return {
message: 'Usuario creado exitosamente',
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role
}
};
}
@Post('/refresh')
async refresh(@Body() body: { refreshToken: string }) {
try {
const tokens = await authService.refreshToken(body.refreshToken);
return tokens;
} catch (error) {
throw new UnauthorizedException('Token de actualización inválido');
}
}
@Get('/me')
@Auth()
async getProfile(@CurrentUser() user: any) {
return {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
permissions: user.permissions
};
}
@Post('/logout')
@Auth()
async logout(@CurrentUser() user: any) {
// Invalidar token de actualización
await authService.invalidateRefreshToken(user.id);
return { message: 'Sesión cerrada exitosamente' };
}
}
app.install('rbac', {
roles: [
{ name: 'admin', level: 100, description: 'Acceso completo al sistema' },
{ name: 'manager', level: 80, description: 'Acceso de gestión de equipos' },
{ name: 'team_lead', level: 60, description: 'Acceso de líder de equipo' },
{ name: 'developer', level: 40, description: 'Acceso de desarrollador' },
{ name: 'viewer', level: 20, description: 'Acceso de solo lectura' }
],
permissions: [
// Permisos de usuarios
'users.create', 'users.read', 'users.update', 'users.delete',
'users.list', 'users.change_role',
// Permisos de tareas
'tasks.create', 'tasks.read', 'tasks.update', 'tasks.delete',
'tasks.assign', 'tasks.complete',
// Permisos de equipos
'teams.create', 'teams.read', 'teams.update', 'teams.delete',
'teams.add_member', 'teams.remove_member',
// Permisos de administrador
'admin.users', 'admin.tasks', 'admin.teams', 'admin.analytics'
]
});

Los roles son jerárquicos basados en su nivel:

  • Admin (100): Acceso completo a todo
  • Manager (80): Puede gestionar equipos y usuarios
  • Team Lead (60): Puede gestionar tareas del equipo
  • Developer (40): Puede crear y actualizar tareas
  • Viewer (20): Acceso de solo lectura
// Requerir autenticación
@Get('/protected')
@Auth()
async protectedRoute(@CurrentUser() user: any) {
return { message: 'Esta ruta está protegida', user: user.username };
}
// Requerir nivel mínimo de rol
@Get('/admin-only')
@MinimumRole('admin')
async adminOnly() {
return { message: 'Acceso de administrador concedido' };
}
@Get('/manager-up')
@MinimumRole('manager')
async managerUp() {
return { message: 'Acceso de nivel manager concedido' };
}
// Permiso único
@Post('/users')
@Permissions('users.create')
async createUser(@Body() userData: any) {
return await userService.create(userData);
}
// Múltiples permisos (el usuario necesita TODOS)
@Put('/users/:id')
@Permissions('users.update', 'users.read')
async updateUser(@Param('id') id: string, @Body() userData: any) {
return await userService.update(id, userData);
}
// Verificaciones complejas de permisos
@Delete('/users/:id')
@Permissions('users.delete')
@MinimumRole('manager') // También requerir rol manager
async deleteUser(@Param('id') id: string) {
return await userService.delete(id);
}
import { Injectable } from 'veloce-ts';
import bcrypt from 'bcrypt';
@Injectable('singleton')
export class UserService {
async findByUsername(username: string): Promise<User | null> {
// Tu lógica de consulta a base de datos
return await this.db.query('SELECT * FROM users WHERE username = ?', [username]);
}
async findById(id: string): Promise<User | null> {
return await this.db.query('SELECT * FROM users WHERE id = ?', [id]);
}
async validateCredentials(username: string, password: string): Promise<User | null> {
const user = await this.findByUsername(username);
if (!user) return null;
const isValid = await this.verifyPassword(password, user.password);
return isValid ? user : null;
}
async hashPassword(password: string): Promise<string> {
return await bcrypt.hash(password, 10);
}
async verifyPassword(password: string, hash: string): Promise<boolean> {
return await bcrypt.compare(password, hash);
}
async create(userData: CreateUserDto): Promise<User> {
const hashedPassword = await this.hashPassword(userData.password);
const user = {
...userData,
password: hashedPassword,
id: generateId(),
createdAt: new Date(),
updatedAt: new Date()
};
await this.db.query(
'INSERT INTO users (id, username, email, password, role, permissions) VALUES (?, ?, ?, ?, ?, ?)',
[user.id, user.username, user.email, user.password, user.role, JSON.stringify(user.permissions)]
);
return user;
}
}
.env
JWT_SECRET=tu-clave-secreta-jwt-super-segura-aqui
JWT_ACCESS_TTL=3600
JWT_REFRESH_TTL=86400
BCRYPT_ROUNDS=12
// Usar requisitos de contraseña fuertes
const PasswordSchema = z.string()
.min(8, 'La contraseña debe tener al menos 8 caracteres')
.regex(/[A-Z]/, 'La contraseña debe contener letra mayúscula')
.regex(/[a-z]/, 'La contraseña debe contener letra minúscula')
.regex(/[0-9]/, 'La contraseña debe contener número')
.regex(/[^A-Za-z0-9]/, 'La contraseña debe contener carácter especial');
// Implementar lista negra de tokens
@Injectable('singleton')
export class TokenService {
private blacklistedTokens = new Set<string>();
blacklistToken(tokenId: string) {
this.blacklistedTokens.add(tokenId);
}
isTokenBlacklisted(tokenId: string): boolean {
return this.blacklistedTokens.has(tokenId);
}
}
import { rateLimit } from 'veloce-ts/middleware';
// Aplicar limitación de velocidad a endpoints de autenticación
app.use('/auth/login', rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 5 // 5 intentos por ventana
}));
// 1. Registrar usuario
POST /auth/register
{
"username": "johndoe",
"email": "john@ejemplo.com",
"password": "SecurePass123!"
}
// 2. Iniciar sesión
POST /auth/login
{
"username": "johndoe",
"password": "SecurePass123!"
}
// Respuesta:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "123",
"username": "johndoe",
"email": "john@ejemplo.com",
"role": "viewer"
}
}
// 3. Usar endpoint protegido
GET /auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// 4. Usar endpoint protegido por rol
GET /admin/dashboard
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
@Controller('/dashboard')
export class DashboardController {
@Get('/')
@Auth()
async getDashboard(@CurrentUser() user: any) {
const baseDashboard = {
user: {
id: user.id,
username: user.username,
role: user.role
}
};
// Añadir datos específicos por rol
if (user.role === 'admin') {
return {
...baseDashboard,
adminStats: await this.getAdminStats(),
systemHealth: await this.getSystemHealth()
};
}
if (user.role === 'manager') {
return {
...baseDashboard,
teamStats: await this.getTeamStats(user.id),
pendingApprovals: await this.getPendingApprovals(user.id)
};
}
return {
...baseDashboard,
myTasks: await this.getUserTasks(user.id),
notifications: await this.getUserNotifications(user.id)
};
}
@Get('/admin-stats')
@MinimumRole('admin')
async getAdminStats() {
return {
totalUsers: await this.userService.count(),
totalTasks: await this.taskService.count(),
systemUptime: process.uptime()
};
}
}