Security Standards¶
Security best practices for SaaS applications.
Authentication¶
Password Requirements¶
// Minimum requirements
const passwordSchema = z.string()
.min(8, 'At least 8 characters')
.regex(/[A-Z]/, 'At least one uppercase')
.regex(/[0-9]/, 'At least one number');
Password Storage¶
// ✅ Always hash passwords
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(password, 12);
const valid = await bcrypt.compare(password, hash);
// ❌ Never store plaintext
user.password = password;
Session Management¶
// Secure session cookies
{
httpOnly: true, // No JavaScript access
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
}
Authorization¶
Multi-Tenancy Isolation¶
// ✅ CRITICAL: Always scope by organization
async function getProjects(orgId: string) {
return db.project.findMany({
where: { organizationId: orgId }
});
}
// ❌ DANGEROUS: Exposes all data
async function getAllProjects() {
return db.project.findMany();
}
Role-Based Access¶
// Define roles
enum Role {
ADMIN = 'admin',
MEMBER = 'member',
VIEWER = 'viewer'
}
// Check permissions
@UseGuards(RolesGuard)
@Roles(Role.ADMIN)
@Delete(':id')
async deleteProject(@Param('id') id: string) {
// Only admins can delete
}
Resource Ownership¶
// Verify ownership before actions
async function updateProject(userId: string, projectId: string, data: any) {
const project = await db.project.findUnique({
where: { id: projectId }
});
// Check user can access this project
const membership = await db.projectMember.findFirst({
where: {
projectId,
userId,
role: { in: ['owner', 'editor'] }
}
});
if (!membership) {
throw new ForbiddenError('Not authorized');
}
return db.project.update({ where: { id: projectId }, data });
}
Input Validation¶
All Inputs Must Be Validated¶
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
password: z.string().min(8),
});
// Validate before processing
const data = createUserSchema.parse(req.body);
SQL Injection Prevention¶
// ✅ Good: Parameterized queries
const user = await db.user.findUnique({
where: { id: userId } // Parameterized
});
// ✅ Good: ORM methods
await db.$queryRaw`
SELECT * FROM users WHERE id = ${userId}
`;
// ❌ Bad: String concatenation
await db.$queryRawUnsafe(
`SELECT * FROM users WHERE id = '${userId}'`
);
XSS Prevention¶
// ✅ Good: React auto-escapes
<div>{userInput}</div>
// ⚠️ Careful: dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: sanitize(content) }} />
// ✅ Good: Sanitize if needed
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirty);
Secrets Management¶
Never Commit Secrets¶
Environment Variables¶
// ✅ Good: Use environment variables
const secret = process.env.AUTH_SECRET;
if (!secret) {
throw new Error('AUTH_SECRET not configured');
}
// ❌ Bad: Hardcoded secrets
const secret = 'my-secret-key-12345';
Secure Generation¶
HTTPS & Transport¶
Force HTTPS¶
// Production: redirect HTTP to HTTPS
if (process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (req.headers['x-forwarded-proto'] !== 'https') {
return res.redirect(`https://${req.headers.host}${req.url}`);
}
next();
});
}
Secure Headers¶
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: true,
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: true,
crossOriginResourcePolicy: true,
dnsPrefetchControl: true,
frameguard: true,
hidePoweredBy: true,
hsts: true,
ieNoOpen: true,
noSniff: true,
referrerPolicy: true,
xssFilter: true,
}));
Rate Limiting¶
import rateLimit from 'express-rate-limit';
// General rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
});
// Stricter for auth endpoints
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10, // 10 attempts per hour
message: 'Too many attempts, try again later',
});
app.use('/api/', limiter);
app.use('/api/auth/', authLimiter);
CORS Configuration¶
// ✅ Good: Specific origins
app.use(cors({
origin: [
'https://app.example.com',
'https://admin.example.com',
],
credentials: true,
}));
// ❌ Bad: Allow all origins
app.use(cors({ origin: '*' }));
Audit Logging¶
Log Security Events¶
// Events to log
await auditLog.create({
organizationId,
userId,
action: 'USER_ROLE_CHANGED',
resourceType: 'User',
resourceId: targetUserId,
details: { oldRole, newRole },
ipAddress: req.ip,
userAgent: req.headers['user-agent'],
});
What to Log¶
| Event | Priority |
|---|---|
| Login attempts (success/fail) | Critical |
| Role changes | Critical |
| Data exports | Critical |
| API key creation | High |
| Password changes | High |
| Settings changes | Medium |
| CRUD operations | Low |
Security Checklist¶
Before deploying:
- All inputs validated
- Authentication required for protected routes
- Authorization checks on all operations
- SQL injection prevented (parameterized queries)
- XSS prevented (sanitized outputs)
- CSRF protection enabled
- Rate limiting configured
- Secrets not in code
- HTTPS enforced
- Secure headers set
- Multi-tenancy isolation enforced
- Audit logging for sensitive operations
- Dependencies updated (no known vulnerabilities)