11 KiB
11 KiB
ADR-016: Security & Authentication Strategy
Status: ✅ Accepted Date: 2025-12-01 Decision Makers: Security Team, System Architect Related Documents: ADR-004: RBAC Implementation, ADR-007: API Design
Context and Problem Statement
LCBP3-DMS จัดการเอกสารสำคัญของโปรเจกต์ ต้องการ Security strategy ที่ครอบคลุม Authentication, Authorization, Data protection, และ Security best practices
ปัญหาที่ต้องแก้:
- Authentication: ใช้วิธีไหนในการยืนยันตัวตน
- Session Management: จัดการ Session อย่างไร
- Password Security: เก็บ Password อย่างไรให้ปลอดภัย
- Data Encryption: Encrypt ข้อมูลอย่างไร
- Security Headers: HTTP Headers ที่ต้องมี
- Input Validation: ป้องกัน Injection attacks
- Rate Limiting: ป้องกัน Brute force attacks
Decision Drivers
- 🔒 Security First: ความปลอดภัยเป็นสำคัญที่สุด
- ✅ Industry Standards: ใช้ Standard practices (OWASP)
- 🎯 User Experience: ไม่ซับซ้อนเกินไป
- 📝 Audit Trail: บันทึก Security events ทั้งหมด
- 🔄 Token Refresh: Session management ที่สะดวก
Decision Outcome
1. Authentication Strategy
Chosen: JWT (JSON Web Tokens) with HTTP-only Cookies
// File: src/auth/auth.service.ts
@Injectable()
export class AuthService {
constructor(
private readonly jwtService: JwtService,
private readonly usersService: UsersService
) {}
async login(credentials: LoginDto): Promise<{ tokens }> {
const user = await this.validateUser(credentials);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
const payload = {
sub: user.user_id,
username: user.username,
roles: user.roles,
};
// Generate tokens
const accessToken = this.jwtService.sign(payload, {
expiresIn: '15m', // Short-lived
});
const refreshToken = this.jwtService.sign(payload, {
secret: process.env.JWT_REFRESH_SECRET,
expiresIn: '7d', // Long-lived
});
// Store refresh token (hashed) in database
await this.storeRefreshToken(user.user_id, refreshToken);
return { accessToken, refreshToken };
}
private async validateUser(credentials: LoginDto) {
const user = await this.usersService.findByUsername(credentials.username);
if (!user) return null;
// Use bcrypt for password comparison
const isValid = await bcrypt.compare(
credentials.password,
user.password_hash
);
return isValid ? user : null;
}
}
2. Password Security
Strategy: bcrypt with salt rounds = 12
import * as bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
// Hash password
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
// Verify password
async function verifyPassword(
password: string,
hash: string
): Promise<boolean> {
return bcrypt.compare(password, hash);
}
Password Policy:
- Minimum 8 characters
- Mix of uppercase, lowercase, numbers
- No common passwords (check against dictionary)
- Password history (last 5 passwords)
- Force change every 90 days (optional)
3. JWT Guard (Authorization)
// File: src/common/guards/jwt-auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
handleRequest(err, user, info) {
if (err || !user) {
throw new UnauthorizedException(info?.message || 'Unauthorized');
}
return user;
}
}
4. Data Encryption
At Rest:
- Database: Use MariaDB encryption at column level (for sensitive fields)
- Files: Encrypt before storing (AES-256)
import * as crypto from 'crypto';
const algorithm = 'aes-256-gcm';
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
function encrypt(text: string): { encrypted: string; iv: string; tag: string } {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
tag: tag.toString('hex'),
};
}
function decrypt(encrypted: string, iv: string, tag: string): string {
const decipher = crypto.createDecipheriv(
algorithm,
key,
Buffer.from(iv, 'hex')
);
decipher.setAuthTag(Buffer.from(tag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
In Transit:
- HTTPS only (TLS 1.3)
- HSTS enabled
- Certificate from trusted CA
5. Security Headers
// File: src/main.ts
import helmet from 'helmet';
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
frameguard: { action: 'deny' },
xssFilter: true,
noSniff: true,
})
);
// CORS
app.enableCors({
origin: process.env.FRONTEND_URL,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
});
6. Input Validation
Strategy: Class-validator + Zod + Custom Sanitization
// DTO Validation
import { IsString, IsEmail, MinLength, Matches } from 'class-validator';
export class LoginDto {
@IsString()
@MinLength(3)
username: string;
@IsString()
@MinLength(8)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
message: 'Password must contain uppercase, lowercase, and number',
})
password: string;
}
// SQL Injection Prevention (TypeORM handles this)
// Use parameterized queries ALWAYS
// XSS Prevention
import * as sanitizeHtml from 'sanitize-html';
function sanitizeInput(input: string): string {
return sanitizeHtml(input, {
allowedTags: [], // No HTML tags
allowedAttributes: {},
});
}
7. Rate Limiting
// File: src/common/guards/rate-limit.guard.ts
import { ThrottlerGuard } from '@nestjs/throttler';
@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
protected getTracker(req: Request): string {
// Track by IP + User ID (if authenticated)
return req.ip + (req.user?.user_id || '');
}
}
// Apply to login endpoint
@Controller('auth')
@UseGuards(CustomThrottlerGuard)
export class AuthController {
@Post('login')
@Throttle(5, 60) // 5 attempts per minute
async login(@Body() credentials: LoginDto) {
return this.authService.login(credentials);
}
}
8. Session Management
Strategy: Stateless JWT + Refresh Token in Database
// Refresh token table
@Entity('refresh_tokens')
export class RefreshToken {
@PrimaryGeneratedColumn()
token_id: number;
@Column()
user_id: number;
@Column()
token_hash: string; // SHA-256 hash of token
@Column()
expires_at: Date;
@Column({ default: false })
is_revoked: boolean;
@CreateDateColumn()
created_at: Date;
}
// Token refresh endpoint
@Post('refresh')
async refresh(@Body('refreshToken') token: string) {
const payload = this.jwtService.verify(token, {
secret: process.env.JWT_REFRESH_SECRET,
});
// Check if token is revoked
const storedToken = await this.findRefreshToken(token);
if (!storedToken || storedToken.is_revoked) {
throw new UnauthorizedException('Invalid refresh token');
}
// Generate new access token
const newAccessToken = this.jwtService.sign({
sub: payload.sub,
username: payload.username,
roles: payload.roles,
});
return { accessToken: newAccessToken };
}
9. Audit Logging (Security Events)
// Log all security-related events
await this.auditLogService.create({
user_id: user.user_id,
action: 'LOGIN_SUCCESS',
entity_type: 'auth',
ip_address: req.ip,
user_agent: req.headers['user-agent'],
});
// Track failed login attempts
await this.auditLogService.create({
action: 'LOGIN_FAILED',
entity_type: 'auth',
ip_address: req.ip,
details: { username: credentials.username },
});
Security Checklist
Application Security
- JWT authentication with short-lived tokens
- Password hashing with bcrypt (12 rounds)
- HTTPS only (TLS 1.3)
- Security headers (Helmet.js)
- CORS properly configured
- Input validation (class-validator)
- SQL injection prevention (TypeORM)
- XSS prevention (sanitize-html)
- CSRF protection (SameSite cookies)
- Rate limiting (Throttler)
Data Security
- Sensitive data encrypted at rest (AES-256)
- Passwords hashed (bcrypt)
- Secrets in environment variables (not in code)
- Database credentials rotated regularly
- Backup encryption enabled
Access Control
- 4-level RBAC implemented
- Principle of least privilege
- Role-based permissions
- Session timeout (15 minutes)
- Audit logging for all actions
Infrastructure
- Firewall configured
- Intrusion detection (optional)
- Regular security updates
- Vulnerability scanning
- Penetration testing (before go-live)
Consequences
Positive Consequences
- ✅ Secure by Design: ใช้ Industry best practices
- ✅ OWASP Compliant: ครอบคลุม OWASP Top 10
- ✅ Audit Trail: บันทึก Security events ทั้งหมด
- ✅ Token-based: Stateless และ Scalable
- ✅ Defense in Depth: หลายชั้นการป้องกัน
Negative Consequences
- ❌ Complexity: Security measures เพิ่ม Complexity
- ❌ Performance: Encryption/Hashing ใช้ CPU
- ❌ User Friction: Password policy อาจรำคาญผู้ใช้
Mitigation Strategies
- Documentation: เขียน Security guidelines ให้ทีม
- Training: อบรม Security awareness
- Automation: Automated security scans
- Monitoring: Real-time security monitoring
Related ADRs
- ADR-004: RBAC Implementation
- ADR-007: API Design & Error Handling
- ADR-015: Deployment & Infrastructure
References
Last Updated: 2025-12-01 Next Review: 2026-03-01 (Quarterly review)