Skip to content

Authentication & Authorization

Learn how to implement secure authentication and role-based access control (RBAC) in your Veloce-TS applications.

Veloce-TS v0.2.6 provides a comprehensive authentication and authorization system:

  • JWT-based Authentication with access and refresh tokens
  • Role-Based Access Control (RBAC) with hierarchical roles
  • Permission-based Authorization for fine-grained access control
  • User Management with password hashing and validation
  • Session Management (optional)
import { VeloceTS } from 'veloce-ts';
const app = new VeloceTS();
app.install('auth', {
jwt: {
secret: process.env.JWT_SECRET || 'your-secret-key',
accessTokenTTL: 3600, // 1 hour
refreshTokenTTL: 86400, // 24 hours
},
userProvider: {
findByCredentials: async (username: string, password: string) => {
// Find user by username/email
const user = await userService.findByUsername(username);
if (!user) return null;
// Verify password
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('Invalid credentials');
}
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('Username already exists');
}
const hashedPassword = await userService.hashPassword(userData.password);
const user = await userService.create({
...userData,
password: hashedPassword,
role: 'viewer', // Default role
permissions: ['tasks.read'] // Default permissions
});
return {
message: 'User created successfully',
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('Invalid refresh token');
}
}
@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) {
// Invalidate refresh token
await authService.invalidateRefreshToken(user.id);
return { message: 'Logged out successfully' };
}
}
app.install('rbac', {
roles: [
{ name: 'admin', level: 100, description: 'Full system access' },
{ name: 'manager', level: 80, description: 'Team management access' },
{ name: 'team_lead', level: 60, description: 'Team lead access' },
{ name: 'developer', level: 40, description: 'Developer access' },
{ name: 'viewer', level: 20, description: 'Read-only access' }
],
permissions: [
// User permissions
'users.create', 'users.read', 'users.update', 'users.delete',
'users.list', 'users.change_role',
// Task permissions
'tasks.create', 'tasks.read', 'tasks.update', 'tasks.delete',
'tasks.assign', 'tasks.complete',
// Team permissions
'teams.create', 'teams.read', 'teams.update', 'teams.delete',
'teams.add_member', 'teams.remove_member',
// Admin permissions
'admin.users', 'admin.tasks', 'admin.teams', 'admin.analytics'
]
});

Roles are hierarchical based on their level:

  • Admin (100): Full access to everything
  • Manager (80): Can manage teams and users
  • Team Lead (60): Can manage team tasks
  • Developer (40): Can create and update tasks
  • Viewer (20): Read-only access
// Require authentication
@Get('/protected')
@Auth()
async protectedRoute(@CurrentUser() user: any) {
return { message: 'This is protected', user: user.username };
}
// Require minimum role level
@Get('/admin-only')
@MinimumRole('admin')
async adminOnly() {
return { message: 'Admin access granted' };
}
@Get('/manager-up')
@MinimumRole('manager')
async managerUp() {
return { message: 'Manager level access granted' };
}
// Single permission
@Post('/users')
@Permissions('users.create')
async createUser(@Body() userData: any) {
return await userService.create(userData);
}
// Multiple permissions (user needs ALL)
@Put('/users/:id')
@Permissions('users.update', 'users.read')
async updateUser(@Param('id') id: string, @Body() userData: any) {
return await userService.update(id, userData);
}
// Complex permission checks
@Delete('/users/:id')
@Permissions('users.delete')
@MinimumRole('manager') // Also require manager role
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> {
// Your database query logic
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=your-super-secret-jwt-key-here
JWT_ACCESS_TTL=3600
JWT_REFRESH_TTL=86400
BCRYPT_ROUNDS=12
// Use strong password requirements
const PasswordSchema = z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[a-z]/, 'Password must contain lowercase letter')
.regex(/[0-9]/, 'Password must contain number')
.regex(/[^A-Za-z0-9]/, 'Password must contain special character');
// Implement token blacklisting
@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';
// Apply rate limiting to auth endpoints
app.use('/auth/login', rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5 // 5 attempts per window
}));
// 1. Register user
POST /auth/register
{
"username": "johndoe",
"email": "john@example.com",
"password": "SecurePass123!"
}
// 2. Login
POST /auth/login
{
"username": "johndoe",
"password": "SecurePass123!"
}
// Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "123",
"username": "johndoe",
"email": "john@example.com",
"role": "viewer"
}
}
// 3. Use protected endpoint
GET /auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// 4. Use role-protected endpoint
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
}
};
// Add role-specific data
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()
};
}
}