2 min read
SaaSSaaS Security & Compliance
SaaS Security Best Practices. SOC 2, GDPR Compliance, Data Encryption und Security Architecture für B2B SaaS.
SaaS SecuritySOC 2GDPRData EncryptionAuthenticationCompliance

SaaS Security & Compliance
Meta-Description: SaaS Security Best Practices. SOC 2, GDPR Compliance, Data Encryption und Security Architecture für B2B SaaS.
Keywords: SaaS Security, SOC 2, GDPR, Data Encryption, Authentication, Compliance, Security Architecture
Einführung
Security ist für B2B SaaS nicht optional – es ist Verkaufsargument. Von SOC 2 bis GDPR erwarten Enterprise-Kunden nachweisbare Sicherheit. Dieser Guide zeigt Security Best Practices und Compliance-Anforderungen.
Security Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ SAAS SECURITY ARCHITECTURE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Defense in Depth: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Layer 1: Network Security │ │
│ │ ├── WAF (Web Application Firewall) │ │
│ │ ├── DDoS Protection │ │
│ │ ├── Rate Limiting │ │
│ │ └── IP Whitelisting (optional) │ │
│ ├─────────────────────────────────────────────────────│ │
│ │ Layer 2: Application Security │ │
│ │ ├── Authentication (MFA, SSO) │ │
│ │ ├── Authorization (RBAC, ABAC) │ │
│ │ ├── Input Validation │ │
│ │ └── CSRF/XSS Protection │ │
│ ├─────────────────────────────────────────────────────│ │
│ │ Layer 3: Data Security │ │
│ │ ├── Encryption at Rest (AES-256) │ │
│ │ ├── Encryption in Transit (TLS 1.3) │ │
│ │ ├── Key Management (KMS) │ │
│ │ └── Data Masking/Tokenization │ │
│ ├─────────────────────────────────────────────────────│ │
│ │ Layer 4: Infrastructure Security │ │
│ │ ├── Private Networks (VPC) │ │
│ │ ├── Security Groups │ │
│ │ ├── Secrets Management │ │
│ │ └── Container Security │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Compliance Frameworks: │
│ ├── SOC 2 Type II │
│ ├── GDPR │
│ ├── HIPAA (Healthcare) │
│ ├── PCI DSS (Payments) │
│ └── ISO 27001 │
│ │
└─────────────────────────────────────────────────────────────┘Authentication & Authorization
// lib/auth/config.ts
import { NextAuthConfig } from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { db } from '@/lib/db';
import Google from 'next-auth/providers/google';
import Credentials from 'next-auth/providers/credentials';
import { compare } from 'bcryptjs';
export const authConfig: NextAuthConfig = {
adapter: PrismaAdapter(db),
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!
}),
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await db.user.findUnique({
where: { email: credentials.email as string },
include: { tenant: true }
});
if (!user || !user.passwordHash) {
return null;
}
const isValid = await compare(
credentials.password as string,
user.passwordHash
);
if (!isValid) {
// Log failed attempt
await logSecurityEvent('failed_login', {
email: credentials.email,
ip: 'unknown' // Get from request
});
return null;
}
// Check if MFA is required
if (user.mfaEnabled) {
return { ...user, requiresMfa: true };
}
return user;
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.tenantId = user.tenantId;
token.role = user.role;
token.permissions = await getUserPermissions(user.id);
}
return token;
},
async session({ session, token }) {
session.user.tenantId = token.tenantId;
session.user.role = token.role;
session.user.permissions = token.permissions;
return session;
}
},
pages: {
signIn: '/auth/login',
error: '/auth/error'
},
session: {
strategy: 'jwt',
maxAge: 24 * 60 * 60 // 24 hours
},
events: {
async signIn({ user, isNewUser }) {
await logSecurityEvent('login', {
userId: user.id,
isNewUser
});
},
async signOut({ token }) {
await logSecurityEvent('logout', {
userId: token.sub
});
}
}
};// lib/auth/mfa.ts
import { authenticator } from 'otplib';
import { db } from '@/lib/db';
import { encrypt, decrypt } from '@/lib/crypto';
export async function setupMFA(userId: string): Promise<{
secret: string;
qrCodeUrl: string;
}> {
const secret = authenticator.generateSecret();
// Encrypt secret before storing
const encryptedSecret = await encrypt(secret);
await db.user.update({
where: { id: userId },
data: {
mfaSecret: encryptedSecret,
mfaEnabled: false // Enable after verification
}
});
const user = await db.user.findUnique({
where: { id: userId },
select: { email: true }
});
const otpAuthUrl = authenticator.keyuri(
user!.email,
'YourApp',
secret
);
return {
secret,
qrCodeUrl: otpAuthUrl
};
}
export async function verifyMFAToken(
userId: string,
token: string
): Promise<boolean> {
const user = await db.user.findUnique({
where: { id: userId },
select: { mfaSecret: true }
});
if (!user?.mfaSecret) {
return false;
}
const secret = await decrypt(user.mfaSecret);
const isValid = authenticator.verify({ token, secret });
if (isValid) {
// Enable MFA if this is setup verification
await db.user.update({
where: { id: userId },
data: { mfaEnabled: true }
});
}
return isValid;
}
// Generate backup codes
export async function generateBackupCodes(userId: string): Promise<string[]> {
const codes: string[] = [];
for (let i = 0; i < 10; i++) {
codes.push(generateSecureCode(8));
}
// Hash and store codes
const hashedCodes = await Promise.all(
codes.map(code => hashCode(code))
);
await db.mfaBackupCode.createMany({
data: hashedCodes.map(hash => ({
userId,
codeHash: hash,
used: false
}))
});
return codes; // Return plain codes to show user once
}Role-Based Access Control
// lib/auth/rbac.ts
export const permissions = {
// Project permissions
'project:read': 'View projects',
'project:create': 'Create projects',
'project:update': 'Update projects',
'project:delete': 'Delete projects',
// User management
'user:read': 'View users',
'user:invite': 'Invite users',
'user:update': 'Update users',
'user:delete': 'Remove users',
// Billing
'billing:read': 'View billing',
'billing:manage': 'Manage billing',
// Admin
'admin:settings': 'Manage settings',
'admin:audit': 'View audit logs'
} as const;
export type Permission = keyof typeof permissions;
export const roles = {
owner: {
name: 'Owner',
permissions: Object.keys(permissions) as Permission[]
},
admin: {
name: 'Admin',
permissions: [
'project:read', 'project:create', 'project:update', 'project:delete',
'user:read', 'user:invite', 'user:update',
'billing:read', 'admin:settings'
] as Permission[]
},
member: {
name: 'Member',
permissions: [
'project:read', 'project:create', 'project:update',
'user:read'
] as Permission[]
},
viewer: {
name: 'Viewer',
permissions: ['project:read', 'user:read'] as Permission[]
}
} as const;
export type Role = keyof typeof roles;
// Permission check middleware
export function requirePermission(permission: Permission) {
return async (req: Request) => {
const session = await getServerSession();
if (!session?.user) {
throw new Error('Unauthorized');
}
const userPermissions = session.user.permissions || [];
if (!userPermissions.includes(permission)) {
await logSecurityEvent('permission_denied', {
userId: session.user.id,
permission,
resource: req.url
});
throw new Error('Forbidden');
}
};
}
// React hook for permission checks
export function usePermission(permission: Permission): boolean {
const { data: session } = useSession();
return session?.user?.permissions?.includes(permission) ?? false;
}Data Encryption
// lib/crypto/encryption.ts
import { createCipheriv, createDecipheriv, randomBytes, scrypt } from 'crypto';
import { promisify } from 'util';
const scryptAsync = promisify(scrypt);
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32;
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
// Derive key from master key
async function deriveKey(masterKey: string, salt: Buffer): Promise<Buffer> {
return scryptAsync(masterKey, salt, KEY_LENGTH) as Promise<Buffer>;
}
export async function encrypt(plaintext: string): Promise<string> {
const masterKey = process.env.ENCRYPTION_KEY!;
const salt = randomBytes(16);
const key = await deriveKey(masterKey, salt);
const iv = randomBytes(IV_LENGTH);
const cipher = createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final()
]);
const authTag = cipher.getAuthTag();
// Combine: salt + iv + authTag + encrypted
const combined = Buffer.concat([salt, iv, authTag, encrypted]);
return combined.toString('base64');
}
export async function decrypt(ciphertext: string): Promise<string> {
const masterKey = process.env.ENCRYPTION_KEY!;
const combined = Buffer.from(ciphertext, 'base64');
// Extract components
const salt = combined.subarray(0, 16);
const iv = combined.subarray(16, 32);
const authTag = combined.subarray(32, 48);
const encrypted = combined.subarray(48);
const key = await deriveKey(masterKey, salt);
const decipher = createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]);
return decrypted.toString('utf8');
}
// Field-level encryption for sensitive data
export class EncryptedField {
static async encrypt(value: string): Promise<string> {
return encrypt(value);
}
static async decrypt(value: string): Promise<string> {
return decrypt(value);
}
}
// Example usage in Prisma
// middleware for automatic encryption
export function encryptionMiddleware() {
return async (params: any, next: any) => {
// Define fields that need encryption
const encryptedFields: Record<string, string[]> = {
User: ['ssn', 'taxId'],
PaymentMethod: ['cardNumber', 'cvv'],
Integration: ['accessToken', 'refreshToken']
};
const fields = encryptedFields[params.model];
if (!fields) return next(params);
// Encrypt on create/update
if (['create', 'update', 'upsert'].includes(params.action)) {
for (const field of fields) {
if (params.args.data?.[field]) {
params.args.data[field] = await encrypt(params.args.data[field]);
}
}
}
const result = await next(params);
// Decrypt on read
if (['findUnique', 'findFirst', 'findMany'].includes(params.action) && result) {
const items = Array.isArray(result) ? result : [result];
for (const item of items) {
for (const field of fields) {
if (item[field]) {
item[field] = await decrypt(item[field]);
}
}
}
}
return result;
};
}Audit Logging
// lib/security/audit.ts
import { db } from '@/lib/db';
import { headers } from 'next/headers';
interface AuditEvent {
action: string;
userId?: string;
tenantId?: string;
resourceType?: string;
resourceId?: string;
details?: Record<string, unknown>;
ip?: string;
userAgent?: string;
}
export async function logSecurityEvent(
action: string,
details: Record<string, unknown> = {}
): Promise<void> {
const headersList = await headers();
const event: AuditEvent = {
action,
details,
ip: headersList.get('x-forwarded-for') || headersList.get('x-real-ip') || 'unknown',
userAgent: headersList.get('user-agent') || 'unknown'
};
await db.auditLog.create({
data: {
...event,
details: event.details,
createdAt: new Date()
}
});
// For critical events, also alert
if (isCriticalEvent(action)) {
await sendSecurityAlert(event);
}
}
function isCriticalEvent(action: string): boolean {
const criticalActions = [
'failed_login_attempt',
'permission_denied',
'data_export',
'user_deleted',
'api_key_created',
'mfa_disabled',
'suspicious_activity'
];
return criticalActions.includes(action);
}
// Audit log query for compliance
export async function getAuditLogs(
tenantId: string,
options: {
startDate?: Date;
endDate?: Date;
action?: string;
userId?: string;
limit?: number;
offset?: number;
}
) {
return db.auditLog.findMany({
where: {
tenantId,
...(options.action && { action: options.action }),
...(options.userId && { userId: options.userId }),
createdAt: {
...(options.startDate && { gte: options.startDate }),
...(options.endDate && { lte: options.endDate })
}
},
orderBy: { createdAt: 'desc' },
take: options.limit || 100,
skip: options.offset || 0
});
}
// Data retention (GDPR compliance)
export async function purgeOldAuditLogs(retentionDays: number = 365): Promise<number> {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
const result = await db.auditLog.deleteMany({
where: {
createdAt: { lt: cutoffDate }
}
});
return result.count;
}GDPR Compliance
// lib/gdpr/data-subject.ts
import { db } from '@/lib/db';
import { encrypt } from '@/lib/crypto';
// Data export (Right to Portability)
export async function exportUserData(userId: string): Promise<object> {
const user = await db.user.findUnique({
where: { id: userId },
include: {
profile: true,
projects: true,
activities: true,
preferences: true
}
});
if (!user) {
throw new Error('User not found');
}
// Remove internal fields
const exportData = {
personalInfo: {
email: user.email,
name: user.name,
createdAt: user.createdAt
},
profile: user.profile,
projects: user.projects.map(p => ({
name: p.name,
createdAt: p.createdAt
})),
preferences: user.preferences,
activityLog: user.activities.slice(0, 1000) // Limit
};
// Log the export
await logSecurityEvent('data_export', {
userId,
dataTypes: Object.keys(exportData)
});
return exportData;
}
// Data deletion (Right to Erasure)
export async function deleteUserData(
userId: string,
options: { hardDelete?: boolean } = {}
): Promise<void> {
const user = await db.user.findUnique({
where: { id: userId }
});
if (!user) {
throw new Error('User not found');
}
if (options.hardDelete) {
// Permanent deletion
await db.$transaction([
db.activity.deleteMany({ where: { userId } }),
db.project.deleteMany({ where: { ownerId: userId } }),
db.profile.delete({ where: { userId } }),
db.user.delete({ where: { id: userId } })
]);
} else {
// Soft delete with anonymization
await db.$transaction([
db.user.update({
where: { id: userId },
data: {
email: `deleted_${userId}@anonymized.local`,
name: 'Deleted User',
deletedAt: new Date(),
passwordHash: null,
mfaSecret: null
}
}),
db.profile.update({
where: { userId },
data: {
bio: null,
avatar: null,
phone: null
}
})
]);
}
await logSecurityEvent('user_deleted', {
userId,
hardDelete: options.hardDelete
});
}
// Consent management
export async function updateConsent(
userId: string,
consents: {
marketing?: boolean;
analytics?: boolean;
thirdParty?: boolean;
}
): Promise<void> {
await db.userConsent.upsert({
where: { userId },
create: {
userId,
...consents,
updatedAt: new Date()
},
update: {
...consents,
updatedAt: new Date()
}
});
await logSecurityEvent('consent_updated', {
userId,
consents
});
}Security Checklist
| Category | Requirement | Priority |
|---|---|---|
| **Auth** | MFA for admins | Critical |
| **Auth** | SSO/SAML support | Important |
| **Data** | Encryption at rest | Critical |
| **Data** | Encryption in transit | Critical |
| **Access** | RBAC implementation | Critical |
| **Audit** | Comprehensive logging | Critical |
| **GDPR** | Data export capability | Required |
| **GDPR** | Data deletion process | Required |
| **Network** | WAF configured | Important |
| **Secrets** | Secure key management | Critical |
Fazit
SaaS Security erfordert:
- Defense in Depth: Mehrere Sicherheitsebenen
- Compliance: SOC 2, GDPR von Anfang an
- Encryption: Daten at rest und in transit
- Audit Trails: Vollständige Nachverfolgbarkeit
Security ist kein Feature, sondern Grundvoraussetzung.
Bildprompts
- "Security architecture layers diagram, defense in depth visualization"
- "SOC 2 compliance dashboard, audit controls checklist"
- "Encryption key management flow, secure vault concept"