Back to Blog
SecurityZero TrustAuthenticationAuthorization

Zero Trust Security: Building Secure Modern Applications

Implement zero trust security architecture. Learn authentication, authorization, and security patterns for modern applications.

B
Bootspring Team
Engineering
February 26, 2026
7 min read

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#

  1. Never trust client input: Validate and sanitize everything
  2. Encrypt data at rest and in transit: Use TLS everywhere
  3. Implement defense in depth: Multiple security layers
  4. Log security events: Maintain audit trails
  5. Regular security reviews: Penetration testing, code audits
  6. Principle of least privilege: Minimum necessary permissions
  7. 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.

Share this article

Help spread the word about Bootspring