Zero trust security assumes no implicit trust, whether inside or outside the network perimeter. This guide covers implementing zero trust principles in modern applications.
Core Principles#
┌─────────────────────────────────────────────────────┐
│ ZERO TRUST │
├─────────────────────────────────────────────────────┤
│ 1. Verify Explicitly │
│ Authenticate and authorize every request │
├─────────────────────────────────────────────────────┤
│ 2. Least Privilege Access │
│ Grant minimum permissions needed │
├─────────────────────────────────────────────────────┤
│ 3. Assume Breach │
│ Minimize blast radius, segment access │
└─────────────────────────────────────────────────────┘
Authentication#
JWT Implementation#
1import jwt from 'jsonwebtoken';
2import { z } from 'zod';
3
4const tokenPayloadSchema = z.object({
5 sub: z.string(),
6 email: z.string().email(),
7 roles: z.array(z.string()),
8 iat: z.number(),
9 exp: z.number(),
10});
11
12type TokenPayload = z.infer<typeof tokenPayloadSchema>;
13
14class AuthService {
15 private accessTokenSecret: string;
16 private refreshTokenSecret: string;
17
18 constructor() {
19 this.accessTokenSecret = process.env.ACCESS_TOKEN_SECRET!;
20 this.refreshTokenSecret = process.env.REFRESH_TOKEN_SECRET!;
21 }
22
23 generateTokens(user: User) {
24 const payload = {
25 sub: user.id,
26 email: user.email,
27 roles: user.roles,
28 };
29
30 const accessToken = jwt.sign(payload, this.accessTokenSecret, {
31 expiresIn: '15m',
32 issuer: 'my-app',
33 audience: 'my-app-users',
34 });
35
36 const refreshToken = jwt.sign(
37 { sub: user.id },
38 this.refreshTokenSecret,
39 { expiresIn: '7d' }
40 );
41
42 return { accessToken, refreshToken };
43 }
44
45 verifyAccessToken(token: string): TokenPayload {
46 try {
47 const decoded = jwt.verify(token, this.accessTokenSecret, {
48 issuer: 'my-app',
49 audience: 'my-app-users',
50 });
51
52 return tokenPayloadSchema.parse(decoded);
53 } catch (error) {
54 throw new AuthenticationError('Invalid token');
55 }
56 }
57
58 async refreshTokens(refreshToken: string) {
59 // Verify refresh token
60 const decoded = jwt.verify(refreshToken, this.refreshTokenSecret);
61
62 // Check if token is revoked
63 const isRevoked = await this.isTokenRevoked(refreshToken);
64 if (isRevoked) {
65 throw new AuthenticationError('Token revoked');
66 }
67
68 // Get fresh user data
69 const user = await this.getUserById(decoded.sub);
70
71 // Revoke old refresh token
72 await this.revokeToken(refreshToken);
73
74 // Generate new tokens
75 return this.generateTokens(user);
76 }
77}Multi-Factor Authentication#
1import { authenticator } from 'otplib';
2
3class MFAService {
4 generateSecret(userEmail: string): { secret: string; uri: string } {
5 const secret = authenticator.generateSecret();
6 const uri = authenticator.keyuri(userEmail, 'MyApp', secret);
7
8 return { secret, uri };
9 }
10
11 verifyToken(secret: string, token: string): boolean {
12 return authenticator.verify({ token, secret });
13 }
14
15 async setupMFA(userId: string) {
16 const user = await db.users.findById(userId);
17 const { secret, uri } = this.generateSecret(user.email);
18
19 // Store encrypted secret
20 await db.users.update(userId, {
21 mfaSecret: encrypt(secret),
22 mfaEnabled: false, // Enable after verification
23 });
24
25 // Generate QR code URL
26 return { uri };
27 }
28
29 async verifyAndEnable(userId: string, token: string) {
30 const user = await db.users.findById(userId);
31 const secret = decrypt(user.mfaSecret);
32
33 if (!this.verifyToken(secret, token)) {
34 throw new Error('Invalid MFA token');
35 }
36
37 await db.users.update(userId, { mfaEnabled: true });
38 }
39}Authorization#
Role-Based Access Control (RBAC)#
1type Permission = 'read' | 'write' | 'delete' | 'admin';
2type Resource = 'users' | 'orders' | 'products' | 'reports';
3
4interface Role {
5 name: string;
6 permissions: Map<Resource, Permission[]>;
7}
8
9const roles: Record<string, Role> = {
10 viewer: {
11 name: 'viewer',
12 permissions: new Map([
13 ['users', ['read']],
14 ['orders', ['read']],
15 ['products', ['read']],
16 ]),
17 },
18 editor: {
19 name: 'editor',
20 permissions: new Map([
21 ['users', ['read']],
22 ['orders', ['read', 'write']],
23 ['products', ['read', 'write']],
24 ]),
25 },
26 admin: {
27 name: 'admin',
28 permissions: new Map([
29 ['users', ['read', 'write', 'delete', 'admin']],
30 ['orders', ['read', 'write', 'delete', 'admin']],
31 ['products', ['read', 'write', 'delete', 'admin']],
32 ['reports', ['read', 'write', 'delete', 'admin']],
33 ]),
34 },
35};
36
37class RBACService {
38 hasPermission(
39 userRoles: string[],
40 resource: Resource,
41 permission: Permission
42 ): boolean {
43 return userRoles.some((roleName) => {
44 const role = roles[roleName];
45 const resourcePermissions = role?.permissions.get(resource);
46 return resourcePermissions?.includes(permission);
47 });
48 }
49}
50
51// Middleware
52function requirePermission(resource: Resource, permission: Permission) {
53 return (req: Request, res: Response, next: NextFunction) => {
54 const userRoles = req.user?.roles || [];
55
56 if (!rbacService.hasPermission(userRoles, resource, permission)) {
57 return res.status(403).json({ error: 'Forbidden' });
58 }
59
60 next();
61 };
62}
63
64// Usage
65app.delete(
66 '/api/products/:id',
67 authenticate,
68 requirePermission('products', 'delete'),
69 deleteProduct
70);Attribute-Based Access Control (ABAC)#
1interface PolicyContext {
2 user: {
3 id: string;
4 roles: string[];
5 department: string;
6 };
7 resource: {
8 type: string;
9 ownerId: string;
10 sensitivity: 'public' | 'internal' | 'confidential';
11 };
12 action: string;
13 environment: {
14 ipAddress: string;
15 time: Date;
16 location: string;
17 };
18}
19
20type Policy = (context: PolicyContext) => boolean;
21
22const policies: Policy[] = [
23 // Users can read their own resources
24 (ctx) =>
25 ctx.action === 'read' && ctx.resource.ownerId === ctx.user.id,
26
27 // Admins can do anything
28 (ctx) =>
29 ctx.user.roles.includes('admin'),
30
31 // Only internal IPs can access confidential resources
32 (ctx) =>
33 ctx.resource.sensitivity !== 'confidential' ||
34 ctx.environment.ipAddress.startsWith('10.'),
35
36 // Department heads can access department resources
37 (ctx) =>
38 ctx.user.roles.includes('department_head') &&
39 ctx.resource.department === ctx.user.department,
40];
41
42class ABACService {
43 evaluate(context: PolicyContext): boolean {
44 return policies.some((policy) => policy(context));
45 }
46}API Security#
Request Validation#
1import { z } from 'zod';
2
3// Define request schemas
4const createUserSchema = z.object({
5 email: z.string().email(),
6 password: z.string().min(12).regex(/[A-Z]/).regex(/[0-9]/),
7 name: z.string().min(2).max(100),
8});
9
10// Validation middleware
11function validateRequest<T>(schema: z.ZodSchema<T>) {
12 return (req: Request, res: Response, next: NextFunction) => {
13 try {
14 req.body = schema.parse(req.body);
15 next();
16 } catch (error) {
17 if (error instanceof z.ZodError) {
18 res.status(400).json({
19 error: 'Validation failed',
20 details: error.errors,
21 });
22 } else {
23 next(error);
24 }
25 }
26 };
27}
28
29app.post('/api/users', validateRequest(createUserSchema), createUser);Security Headers#
1import helmet from 'helmet';
2
3app.use(helmet({
4 contentSecurityPolicy: {
5 directives: {
6 defaultSrc: ["'self'"],
7 scriptSrc: ["'self'", "'unsafe-inline'"],
8 styleSrc: ["'self'", "'unsafe-inline'"],
9 imgSrc: ["'self'", "data:", "https:"],
10 connectSrc: ["'self'", "https://api.example.com"],
11 frameSrc: ["'none'"],
12 objectSrc: ["'none'"],
13 },
14 },
15 hsts: {
16 maxAge: 31536000,
17 includeSubDomains: true,
18 preload: true,
19 },
20 referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
21}));
22
23// Additional custom headers
24app.use((req, res, next) => {
25 res.setHeader('X-Content-Type-Options', 'nosniff');
26 res.setHeader('X-Frame-Options', 'DENY');
27 res.setHeader('X-XSS-Protection', '1; mode=block');
28 res.setHeader('Permissions-Policy', 'geolocation=(), camera=()');
29 next();
30});Service-to-Service Security#
mTLS Authentication#
1import https from 'https';
2import fs from 'fs';
3
4// Server setup with mTLS
5const serverOptions = {
6 key: fs.readFileSync('./certs/server-key.pem'),
7 cert: fs.readFileSync('./certs/server-cert.pem'),
8 ca: fs.readFileSync('./certs/ca-cert.pem'),
9 requestCert: true,
10 rejectUnauthorized: true,
11};
12
13const server = https.createServer(serverOptions, app);
14
15// Client with certificate
16const clientOptions = {
17 key: fs.readFileSync('./certs/client-key.pem'),
18 cert: fs.readFileSync('./certs/client-cert.pem'),
19 ca: fs.readFileSync('./certs/ca-cert.pem'),
20};
21
22async function callService(url: string) {
23 const agent = new https.Agent(clientOptions);
24 return fetch(url, { agent });
25}Service Mesh (Istio example)#
1# AuthorizationPolicy
2apiVersion: security.istio.io/v1beta1
3kind: AuthorizationPolicy
4metadata:
5 name: order-service-policy
6spec:
7 selector:
8 matchLabels:
9 app: order-service
10 rules:
11 - from:
12 - source:
13 principals: ["cluster.local/ns/default/sa/frontend"]
14 to:
15 - operation:
16 methods: ["GET", "POST"]
17 paths: ["/api/orders/*"]Secrets Management#
1import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
2
3class SecretsManager {
4 private client: SecretManagerServiceClient;
5 private cache: Map<string, { value: string; expiresAt: number }> = new Map();
6
7 constructor() {
8 this.client = new SecretManagerServiceClient();
9 }
10
11 async getSecret(name: string): Promise<string> {
12 const cached = this.cache.get(name);
13 if (cached && cached.expiresAt > Date.now()) {
14 return cached.value;
15 }
16
17 const [version] = await this.client.accessSecretVersion({
18 name: `projects/${process.env.PROJECT_ID}/secrets/${name}/versions/latest`,
19 });
20
21 const value = version.payload?.data?.toString() || '';
22
23 // Cache for 5 minutes
24 this.cache.set(name, {
25 value,
26 expiresAt: Date.now() + 5 * 60 * 1000,
27 });
28
29 return value;
30 }
31}Audit Logging#
1interface AuditEvent {
2 timestamp: Date;
3 actor: {
4 id: string;
5 type: 'user' | 'service' | 'system';
6 ip?: string;
7 };
8 action: string;
9 resource: {
10 type: string;
11 id: string;
12 };
13 outcome: 'success' | 'failure';
14 metadata?: Record<string, unknown>;
15}
16
17class AuditLogger {
18 async log(event: AuditEvent) {
19 // Write to immutable audit log
20 await db.auditLogs.create({
21 data: {
22 ...event,
23 timestamp: new Date(),
24 hash: this.computeHash(event),
25 },
26 });
27 }
28
29 private computeHash(event: AuditEvent): string {
30 const previous = await this.getLastHash();
31 return crypto
32 .createHash('sha256')
33 .update(JSON.stringify(event) + previous)
34 .digest('hex');
35 }
36}
37
38// Usage in middleware
39app.use((req, res, next) => {
40 res.on('finish', () => {
41 auditLogger.log({
42 timestamp: new Date(),
43 actor: {
44 id: req.user?.id || 'anonymous',
45 type: 'user',
46 ip: req.ip,
47 },
48 action: `${req.method} ${req.path}`,
49 resource: {
50 type: 'api',
51 id: req.path,
52 },
53 outcome: res.statusCode < 400 ? 'success' : 'failure',
54 });
55 });
56 next();
57});Best Practices#
- Never trust client input: Validate and sanitize everything
- Encrypt data at rest and in transit: Use TLS everywhere
- Implement defense in depth: Multiple security layers
- Log security events: Maintain audit trails
- Regular security reviews: Penetration testing, code audits
- Principle of least privilege: Minimum necessary permissions
- Rotate secrets regularly: Automate credential rotation
Conclusion#
Zero trust requires continuous verification at every layer. Start with strong authentication and authorization, secure service-to-service communication, and maintain comprehensive audit logs. Security is not a feature—it's a continuous practice.