Part 3: API Security Design
| Item | Content |
|---|---|
| Document Name | Part 3: API Security Design |
| Product Name | DTA Wide Sleep Management Platform |
| Date | 2026-02-10 |
| Scope | Part 3 (Backend API) |
1. API Security Overview
Objective: Comply with OWASP API Top 10, secure RESTful API design
Security Principles:
- Authentication/authorization mandatory (JWT Bearer Token)
- Input validation (DTO + class-validator)
- Rate Limiting (Redis-based)
- Minimal error message information disclosure
- Additional protection for admin endpoints
2. Authentication and Authorization
2.1 JWT-Based Authentication
AppToken Structure (RS256 Asymmetric Signing):
// AppToken Payload (Actual Implementation - app-token.guard.ts)
{
"appId": "app-uuid",
"deviceId": "device-uuid",
"jti": "token-unique-id",
"exp": 1707579000, // Expire after 30 minutes
"iat": 1707577200
}
Authentication Flow:
Token Revocation Policy:
Inactivity Detection Implementation Status:
| Item | Notes |
|---|---|
| AccessToken TTL 30-minute expiration | Based on JWT exp claim |
| RefreshToken TTL 14-day expiration | Based on JWT exp claim, long-term inactivity |
2.2 Role-Based Authorization (RBAC)
Decorator Usage:
@Controller('sleep')
@UseGuards(AppTokenGuard, FlexibleAuthGuard)
export class SleepController {
@Get('/logs')
@Roles('patient', 'clinician', 'admin')
async getSleepLogs(@CurrentUser() user: TokenPayload) {
// Patient: own data only
// Clinician: assigned patients only
// Admin: all data
}
@Delete('/logs/:id')
@Roles('patient', 'admin')
async deleteSleepLog(@Param('id') id: string) {
// Patient: delete own data only
// Admin: delete all data
}
}
2.3 App Request Integrity and Authenticity Verification
Currently Implemented Validation Layers:
| Validation Layer | Implementation Status | Reference File | Notes |
|---|---|---|---|
| AppToken (RS256) App Authenticity Verification | ✅ Implemented | app-token.guard.ts, app-token.service.ts | JWK-based RS256 signing + DB status check |
| Device ID SHA-256 Hash Verification | ✅ Implemented | device-identifier.service.ts | Request deviceIdHash vs server calculated value comparison |
| PubSub/Webhook GCP OIDC JWT Verification | ✅ Implemented | pubsub.guard.ts, webhook-validation.service.ts | GCP Pub/Sub request authentication |
| HMAC-based Request Body Signature Verification | ❌ Not implemented | - | No additional signature beyond AppToken |
| Response Body HMAC/Signature Addition | ❌ Not implemented | - | No signature layer on responses |
3. Input Validation
3.1 DTO-Based Validation
CreateSleepLogDto:
import { IsString, IsDateString, IsOptional, IsInt, Min, Max } from 'class-validator';
export class CreateSleepLogDto {
@IsDateString()
bedtime: string; // ISO 8601 format
@IsDateString()
wakeTime: string;
@IsOptional()
@IsInt()
@Min(0)
@Max(480) // Maximum 8 hours
sol?: number; // Sleep Onset Latency (minutes)
@IsOptional()
@IsString()
@MaxLength(500)
notes?: string;
}
ValidationPipe Configuration:
// main.ts
app.useGlobalPipes(
new ValidationPipe({
transform: true, // Auto type conversion
whitelist: true, // Remove non-DTO properties
forbidNonWhitelisted: true, // Error on extra properties
exceptionFactory: (errors) => {
// Custom error response
return new BadRequestException({
code: 'VALIDATION_ERROR',
message: 'Input validation failed',
details: errors.map(e => ({
property: e.property,
constraints: e.constraints
}))
});
}
})
);
3.2 SQL Injection Prevention
Prisma ORM Parameterization:
// ✅ SAFE: Prisma parameterized query
async findByUserId(userId: string): Promise<SleepLog[]> {
return this.prisma.sleepLog.findMany({
where: { userId: userId } // Auto parameterized
});
}
3.3 XSS Prevention
HTML Escaping:
import { sanitize } from 'class-sanitizer';
export class CreateNoteDto {
@IsString()
@MaxLength(1000)
@sanitize() // Remove HTML tags
content: string;
}
// Or manual escaping
function syntax(text: string): string {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
4. Rate Limiting and Anti-Abuse
4.1 Rate Limiting Policy
| Endpoint Type | Limit | Window | On Exceeded |
|---|---|---|---|
| Authentication (Login) | 5 req/minute | Per user | 429 + 1 min wait |
| Data Retrieval (GET) | 100 req/minute | Per user | 429 + retry guidance |
| Data Creation (POST) | 10 req/minute | Per user | 429 + retry guidance |
| Admin API | 60 req/minute | Per user | 429 + logging |
| Public API | 1000 req/minute | Per IP | 429 + CAPTCHA |
4.2 Redis-Based Rate Limiter
Implementation (NestJS Interceptor):
@Injectable()
export class RateLimitInterceptor implements NestInterceptor {
constructor(private readonly redis: RedisService) {}
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest();
const userId = request.user?.userId || request.ip;
const key = `rate-limit:${request.path}:${userId}`;
const current = await this.redis.incr(key);
if (current === 1) {
await this.redis.expire(key, 60); // 1 min TTL
}
const limit = this.getLimit(request.path);
if (current > limit) {
throw new TooManyRequestsException(`Rate limit exceeded. Try again in 60 seconds.`);
}
return next.handle();
}
}
4.3 Brute Force Protection
Login Failure Limit:
async login(email: string, password: string) {
const key = `login-attempts:${email}`;
const attempts = await this.redis.get(key);
if (attempts && parseInt(attempts) >= 5) {
throw new TooManyRequestsException('Too many login attempts. Try again in 15 minutes.');
}
// Password verification
const isValid = await this.verifyPassword(email, password);
if (!isValid) {
await this.redis.incr(key);
await this.redis.expire(key, 900); // 15 minutes
throw new UnauthorizedException('Invalid credentials');
}
// Reset counter on success
await this.redis.del(key);
return this.generateToken(user);
}
5. Error Message Policy
5.1 Safe Error Response
Production Environment:
// ✅ SAFE: Minimal information disclosure
{
"code": 2051,
"message": "INVALID_TOKEN",
"detail": "The token is invalid.",
"timestamp": "2026-02-10T12:00:00.000Z"
}
Global Exception Filter:
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
let status = 500;
let code = 'INTERNAL_SERVER_ERROR';
let detail = 'A server error occurred.';
if (exception instanceof HttpException) {
status = exception.getStatus();
const exceptionResponse = exception.getResponse();
code = exceptionResponse['code'] || exception.message;
detail = exceptionResponse['detail'] || exception.message;
}
// ❌ Absolutely never expose stack traces
// ❌ Absolutely never expose database connection info
// ❌ Absolutely never expose file paths
response.status(status).json({
code,
message: code,
detail,
timestamp: new Date().toISOString()
});
// Internal logging (Cloud Logging / LoggerService)
if (status >= 500) {
this.logger.error(exception);
}
}
}
| Item | Implementation Status | Notes |
|---|---|---|
| Error message minimization (no stack trace exposure) | ✅ Implemented | GlobalExceptionFilter |
5.2 HTTP Status Code Mapping
| Scenario | HTTP Status | Response Code | Message |
|---|---|---|---|
| Authentication failed (no token) | 401 | AUTH_REQUIRED | "Authentication required." |
| Authentication failed (token expired) | 401 | TOKEN_EXPIRED | "Token has expired." |
| Permission denied | 403 | FORBIDDEN | "Permission denied." |
| Resource not found | 404 | NOT_FOUND | "Resource not found." |
| Input validation failed | 400 | VALIDATION_ERROR | "Input value is invalid." |
| Rate limit exceeded | 429 | RATE_LIMIT_EXCEEDED | "Request limit exceeded." |
| Server error | 500 | INTERNAL_ERROR | "A server error occurred." |
6. Admin Endpoint Protection
6.1 Additional Security Layer
Admin-only Guard:
@Injectable()
export class AdminGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.user;
// 1. Check Admin role
if (!user.roles?.includes('admin')) {
return false;
}
// 2. IP whitelist check (optional)
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
if (!this.isAllowedIp(clientIp)) {
this.logger.warn(`Admin access from unauthorized IP: ${clientIp}`);
return false;
}
// 3. Time-based access restriction (optional)
const hour = new Date().getUTCHours();
if (hour >= 0 && hour < 6) {
this.logger.warn(`Admin access during restricted hours: ${user.userId}`);
// Warn only, don't block
}
return true;
}
}
6.2 Sensitive API Endpoint List
| Endpoint | Role | Additional Security | Audit Log |
|---|---|---|---|
DELETE /v1/admin/users/:id | Admin | MFA reconfirmation | ✅ |
POST /v1/admin/users/:id/roles | Admin | Jira approval | ✅ |
GET /v1/admin/audit-logs | Admin | IP whitelist | ✅ |
POST /v1/admin/system/settings | Admin | Jira approval | ✅ |
GET /v1/admin/users/:id/data-export | Admin, CS | Data anonymization | ✅ |
7. API Security Checklist
| # | Item | Implementation | Validation |
|---|---|---|---|
| 1 | All endpoints require authentication (except public) | ✅ | API Test |
| 2 | Role-based authorization (RBAC) | ✅ | API Test |
| 3 | DTO input validation (class-validator) | ✅ | API Test |
| 4 | SQL Injection prevention (Prisma ORM) | ✅ | SAST |
| 5 | XSS prevention (HTML escaping) | ✅ | DAST |
| 6 | CSRF prevention (SameSite Cookie) | N/A | JWT Bearer Token used |
| 7 | Rate Limiting (Redis) | ✅ | API Test |
| 8 | Safe error messages | ✅ | API Test |
| 9 | HTTPS enforcement (TLS 1.3) | ✅ | GCP SSL |
| 10 | Sensitive data logging prohibition | ✅ | Log sample validation |
| 11 | CORS appropriate configuration | ✅ | Configuration review |
| 12 | API versioning (v1/) | ✅ | URL pattern |
| 13 | Admin API additional protection | ✅ | Admin Guard |
| 14 | Audit logging (sensitive actions) | ✅ | Log sample |
8. OWASP API Top 10 Compliance
| # | OWASP API Top 10 | Implementation Control | Status |
|---|---|---|---|
| 1 | Broken Object Level Authorization | userId validation, RBAC | ✅ |
| 2 | Broken Authentication | JWT, Rate Limiting | ✅ |
| 3 | Broken Object Property Level Authorization | DTO Whitelist | ✅ |
| 4 | Unrestricted Resource Consumption | Rate Limiting, pagination | ✅ |
| 5 | Broken Function Level Authorization | Roles Guard | ✅ |
| 6 | Unrestricted Access to Sensitive Business Flows | Rate Limiting, block access | ✅ |
| 7 | Server Side Request Forgery (SSRF) | URL validation, whitelist | ✅ |
| 8 | Security Misconfiguration | Secure defaults, SAST | ✅ |
| 9 | Improper Inventory Management | Swagger docs, API versioning | ✅ |
| 10 | Unsafe Consumption of APIs | External API TLS validation, timeout | ✅ |
9. Swagger API Documentation Security
9.1 Swagger UI Access Restriction
Production Environment:
// main.ts
if (process.env.NODE_ENV !== 'production') {
// Enable Swagger only in staging/development
const config = new DocumentBuilder()
.setTitle('DTA Wide API')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('v1/docs', app, document);
} else {
// Production: Disable Swagger or IP whitelist
// [TODO: Production Swagger disabling verification needed]
}
9.2 Hide Sensitive Information
@ApiProperty({
example: 'user@example.com',
description: 'User email'
// ❌ Never include: actual emails, tokens, passwords
})
email: string;
@ApiProperty({
example: '********', // Masked example
writeOnly: true, // Exclude from response
})
@Exclude() // Auto-exclude with class-transformer
password: string;
Evidence and References (Artifacts)
- API Security Checklist (Section 7 of this document)
- DTO Validation Code -
apps/dta-wide-api/src/app/*/dto/*.dto.ts - Guards Implementation -
guards/app-token.guard.ts,guards/flexible-auth.guard.ts,guards/webhook.guard.ts - Rate Limiter Code -
libs/core/redis/src/lib/rate-limit.interceptor.ts - Exception Filter Code -
apps/dta-wide-api/src/app/filters/global-exception.filter.ts - OWASP ZAP DAST Report -
reports/api-dast-latest.pdf - Swagger API Documentation -
https://staging-api.dta-wide.com/v1/docs(staging) - API Security Test Results -
test-results/api-security-tests.log - Rate Limiting Test - k6 script + results
- Audit Log Sample (admin API) -
logs/admin-api-calls.log
| Test Item | Tool | Result | Notes |
|---|---|---|---|
| SQL Injection | OWASP ZAP | [TODO: Execution needed] | Prisma ORM (parameterized) |
| XSS (Reflected) | OWASP ZAP | [TODO: Execution needed] | HTML escaping |
| Broken Authentication | Burp Suite | [TODO: Execution needed] | AppToken RS256 + Rate Limit |
| IDOR (Insecure Direct Object Reference) | Manual Test | [TODO: Execution needed] | userId validation |
| Rate Limiting | k6 Load Test | [TODO: Execution needed] | 100 req/min per user |
| CSRF | OWASP ZAP | N/A | JWT Bearer Token (no cookies) |
| Sensitive Information Disclosure | Manual Review | [TODO: Execution needed] | Error message minimization |