Auth & Guard Patterns
A skill for implementing authentication and authorization following traceability-backend security patterns.
JWT Token Structure
The JwtPayloadDto contains:
typescript
interface JwtPayloadDto {
user_id: string;
organization_id: string;
organization: string; // display_id (tenant)
organization_type: string; // FARMER, COLLECTOR, etc.
roles: string[];
}
Custom Decorators
@AuthUser()
Extract authenticated user from request:
typescript
import { AuthUser, JwtPayloadDto } from '@app/auth-utilities';
@Get('profile')
async getProfile(@AuthUser() user: JwtPayloadDto) {
// user.user_id, user.organization_id, user.roles
}
@Roles()
Restrict access by role:
typescript
import { Roles } from '@app/auth-utilities';
@Post('admin')
@Roles('ADMIN', 'SUPER_ADMIN')
async adminAction() { }
@OrganizationTypes()
Restrict by organization type:
typescript
import { OrganizationTypes } from '../common/guard/organization-type.guard';
@Post('collector-only')
@OrganizationTypes('COLLECTOR')
async collectorAction() { }
Guards
JWT Authentication
typescript
import { AuthGuard } from '@nestjs/passport';
@UseGuards(AuthGuard('jwt-token'))
@ApiBearerAuth()
async protectedEndpoint() { }
Role-Based Access Control
typescript
import { RolesGuard } from '@app/auth-utilities';
@UseGuards(AuthGuard('jwt-token'), RolesGuard)
@Roles('ADMIN')
async adminOnly() { }
Tenant Validation
typescript
import { TenantGuard } from '../common/guard/tenant.guard';
@UseGuards(TenantGuard)
async tenantSpecific() {
// Validates tenant from subdomain matches token
}
Organization Type Guard
typescript
import { OrganizationTypeGuard } from '../common/guard/organization-type.guard';
@UseGuards(OrganizationTypeGuard)
@OrganizationTypes('FARMER', 'COLLECTOR')
async multiTypeAccess() { }
Complete Auth Controller Example
typescript
import { Controller, Post, Body, UseGuards, Version, HttpCode } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { AuthGuard } from '@nestjs/passport';
import { AuthUser, JwtPayloadDto } from '@app/auth-utilities';
import { MyService } from './my.service';
@ApiTags('MyFeature')
@Controller('my-feature')
export class MyController {
constructor(private readonly service: MyService) {}
@Post('public')
@Version('1')
@ApiOperation({ summary: 'Public endpoint' })
async publicEndpoint(@Body() dto: PublicDto) {
return this.service.publicAction(dto);
}
@Post('protected')
@Version('1')
@UseGuards(AuthGuard('jwt-token'))
@ApiBearerAuth()
@ApiOperation({ summary: 'Protected endpoint' })
async protectedEndpoint(
@AuthUser() user: JwtPayloadDto,
@Body() dto: ProtectedDto
) {
return this.service.protectedAction(user, dto);
}
@Post('admin-only')
@Version('1')
@UseGuards(AuthGuard('jwt-token'), RolesGuard)
@ApiBearerAuth()
@Roles('ADMIN')
@ApiOperation({ summary: 'Admin only endpoint' })
async adminEndpoint(@AuthUser() user: JwtPayloadDto) {
return this.service.adminAction(user);
}
}
Password Utilities
typescript
import { hashPassword, verifyPassword } from '@app/auth-utilities';
// Hash password
const hashed = hashPassword('plainPassword');
// Verify password
const isValid = verifyPassword('plainPassword', hashedPassword);
Token Management
Access Token
typescript
// Generated with user info and roles
const tokenResult = this.tokenService.generateAccessToken(
userId,
organizationId,
organizationDisplayId,
organizationType,
userRoles
);
// Returns: { access_token, expires_at }
Refresh Token
typescript
// Generate
const refreshToken = this.tokenService.generateRefreshToken();
// Returns: { token, expires_at }
// Store
await this.tokenService.storeRefreshToken(token, userId, organizationId);
// Validate
const payload = await this.tokenService.validateRefreshToken(token);
// Revoke
await this.tokenService.revokeRefreshToken(token);
Common Auth Patterns
Rate Limiting with Cache
typescript
@Injectable()
export class MyService {
private readonly maxAttempts = 5;
private readonly lockoutDuration = 15 * 60; // 15 minutes
async checkRateLimit(identifier: string) {
const key = `attempts:${identifier}`;
const attempts = await this.cacheManager.get(key);
if (attempts >= this.maxAttempts) {
throw new UnauthorizedException('Too many attempts');
}
await this.cacheManager.set(key, attempts + 1, 3600 * 1000);
}
}
Multi-Tenant Patterns
typescript
// Get tenant from Fastify request
const tenant = req.raw['tenant'];
// Match against user's organization
if (tenant !== user.organization) {
throw new ForbiddenException('Invalid tenant');
}
Security Best Practices
- •Always validate JWT on protected endpoints with
@UseGuards(AuthGuard('jwt-token')) - •Use @ApiBearerAuth() for Swagger documentation
- •Log sensitive actions with
@LogActivity()decorator - •Use Transactions for multi-step auth operations
- •Hash passwords before storing
- •Implement rate limiting for auth endpoints
- •Validate tenant matches token in multi-tenant apps
- •Use soft deletes - check
deleted_at: null - •Never expose internal IDs in API responses
- •Set appropriate CORS origins with regex support