Security
Security in microservices is more complex than monoliths due to increased attack surface. Every service-to-service call needs authentication and authorization.Learning Objectives:
- Implement JWT-based authentication
- Design authorization strategies
- Set up mutual TLS (mTLS)
- Manage secrets securely
- Protect against common attacks
Security Challenges in Microservices
Copy
┌─────────────────────────────────────────────────────────────────────────────┐
│ MICROSERVICES SECURITY CHALLENGES │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ATTACK SURFACE │
│ ────────────── │
│ │
│ Monolith: Microservices: │
│ ┌────────────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ │ │ │──│ │──│ │ │
│ │ Single │ vs │ A │ │ B │ │ C │ │
│ │ Entry │ │ │──│ │──│ │ │
│ │ │ └─────┘ └─────┘ └─────┘ │
│ └────────────┘ │ │ │ │
│ │ └────────┴────────┘ │
│ 1 entry point N entry points + M connections │
│ │
│ │
│ CHALLENGES: │
│ ──────────── │
│ │
│ • How do services authenticate each other? │
│ • How to propagate user identity across services? │
│ • Where to enforce authorization? │
│ • How to secure service-to-service communication? │
│ • How to manage secrets across many services? │
│ • How to audit access across distributed system? │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Authentication Strategies
JWT-Based Authentication
Copy
┌─────────────────────────────────────────────────────────────────────────────┐
│ JWT AUTHENTICATION FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. User Login │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Client │──login─▶│ Auth Service │ │
│ │ │◀─JWT────│ (issues JWT) │ │
│ └──────────┘ └──────────────┘ │
│ │
│ 2. Access Services │
│ ┌──────────┐ Authorization: Bearer <JWT> ┌──────────────┐ │
│ │ Client │───────────────────────────────▶│ API Gateway │ │
│ └──────────┘ └──────┬───────┘ │
│ │ │
│ 3. JWT Propagation │ JWT │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Order │─JWT─▶│ Payment │─JWT─▶│ Inventory│ │
│ │ Service │ │ Service │ │ Service │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ JWT Structure: │
│ ───────────── │
│ Header.Payload.Signature │
│ │
│ Payload: { │
│ "sub": "user123", │
│ "email": "[email protected]", │
│ "roles": ["customer", "premium"], │
│ "permissions": ["orders:read", "orders:write"], │
│ "exp": 1705347600, │
│ "iss": "auth-service" │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
JWT Service Implementation
Copy
// services/AuthService.js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
class AuthService {
constructor(options = {}) {
this.accessTokenSecret = process.env.JWT_ACCESS_SECRET;
this.refreshTokenSecret = process.env.JWT_REFRESH_SECRET;
this.accessTokenExpiry = options.accessTokenExpiry || '15m';
this.refreshTokenExpiry = options.refreshTokenExpiry || '7d';
this.issuer = options.issuer || 'auth-service';
}
async login(email, password) {
const user = await this.userRepository.findByEmail(email);
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
throw new UnauthorizedError('Invalid credentials');
}
const tokens = this.generateTokens(user);
// Store refresh token
await this.tokenRepository.storeRefreshToken(
user.id,
tokens.refreshToken,
this.refreshTokenExpiry
);
return tokens;
}
generateTokens(user) {
const payload = {
sub: user.id,
email: user.email,
roles: user.roles,
permissions: this.getPermissionsForRoles(user.roles)
};
const accessToken = jwt.sign(payload, this.accessTokenSecret, {
expiresIn: this.accessTokenExpiry,
issuer: this.issuer,
audience: 'microservices'
});
const refreshToken = jwt.sign(
{ sub: user.id, type: 'refresh' },
this.refreshTokenSecret,
{
expiresIn: this.refreshTokenExpiry,
issuer: this.issuer
}
);
return { accessToken, refreshToken };
}
async refreshAccessToken(refreshToken) {
try {
const decoded = jwt.verify(refreshToken, this.refreshTokenSecret);
// Verify token exists in store (not revoked)
const storedToken = await this.tokenRepository.getRefreshToken(decoded.sub);
if (storedToken !== refreshToken) {
throw new UnauthorizedError('Invalid refresh token');
}
const user = await this.userRepository.findById(decoded.sub);
if (!user) {
throw new UnauthorizedError('User not found');
}
return this.generateTokens(user);
} catch (error) {
throw new UnauthorizedError('Invalid refresh token');
}
}
async logout(userId, refreshToken) {
// Revoke refresh token
await this.tokenRepository.revokeRefreshToken(userId);
// Optionally add access token to blacklist
// await this.tokenRepository.blacklistAccessToken(accessToken);
}
getPermissionsForRoles(roles) {
const rolePermissions = {
admin: ['*'],
customer: [
'orders:read',
'orders:create',
'profile:read',
'profile:update'
],
support: [
'orders:read',
'users:read',
'tickets:*'
]
};
const permissions = new Set();
roles.forEach(role => {
const perms = rolePermissions[role] || [];
perms.forEach(p => permissions.add(p));
});
return Array.from(permissions);
}
}
module.exports = { AuthService };
JWT Middleware
Copy
// middleware/auth.js
const jwt = require('jsonwebtoken');
function authenticate(options = {}) {
const { secret = process.env.JWT_ACCESS_SECRET, optional = false } = options;
return async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
if (optional) {
return next();
}
return res.status(401).json({ error: 'Missing authorization header' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, secret, {
issuer: 'auth-service',
audience: 'microservices'
});
// Check token blacklist
const isBlacklisted = await tokenBlacklist.check(token);
if (isBlacklisted) {
return res.status(401).json({ error: 'Token revoked' });
}
req.user = {
id: decoded.sub,
email: decoded.email,
roles: decoded.roles,
permissions: decoded.permissions
};
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(401).json({ error: 'Invalid token' });
}
};
}
// Usage
app.use('/api', authenticate());
Authorization Patterns
Role-Based Access Control (RBAC)
Copy
// middleware/authorize.js
function authorize(...allowedRoles) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
const hasRole = req.user.roles.some(role => allowedRoles.includes(role));
if (!hasRole) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Usage
app.get('/admin/users', authenticate(), authorize('admin'), (req, res) => {
// Only admins can access
});
app.get('/orders', authenticate(), authorize('customer', 'support', 'admin'), (req, res) => {
// Customers, support, and admins can access
});
Permission-Based Authorization
Copy
// middleware/permission.js
function checkPermission(...requiredPermissions) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
const userPermissions = req.user.permissions;
// Check for wildcard permission
if (userPermissions.includes('*')) {
return next();
}
const hasPermission = requiredPermissions.every(required => {
// Check exact match
if (userPermissions.includes(required)) {
return true;
}
// Check wildcard match (e.g., 'orders:*' matches 'orders:read')
const [resource, action] = required.split(':');
if (userPermissions.includes(`${resource}:*`)) {
return true;
}
return false;
});
if (!hasPermission) {
return res.status(403).json({
error: 'Insufficient permissions',
required: requiredPermissions,
have: userPermissions
});
}
next();
};
}
// Usage
app.post('/orders',
authenticate(),
checkPermission('orders:create'),
createOrder
);
app.delete('/orders/:id',
authenticate(),
checkPermission('orders:delete'),
deleteOrder
);
Attribute-Based Access Control (ABAC)
Copy
// authorization/PolicyEngine.js
class PolicyEngine {
constructor() {
this.policies = [];
}
addPolicy(policy) {
this.policies.push(policy);
}
evaluate(context) {
for (const policy of this.policies) {
const result = policy.evaluate(context);
if (result.effect === 'DENY') {
return { allowed: false, reason: result.reason };
}
if (result.effect === 'ALLOW') {
return { allowed: true };
}
}
return { allowed: false, reason: 'No matching policy' };
}
}
// Policy definitions
const policies = [
{
name: 'owner-access',
evaluate: (context) => {
const { user, resource, action } = context;
if (resource.ownerId === user.id) {
return { effect: 'ALLOW' };
}
return { effect: 'CONTINUE' };
}
},
{
name: 'admin-access',
evaluate: (context) => {
const { user } = context;
if (user.roles.includes('admin')) {
return { effect: 'ALLOW' };
}
return { effect: 'CONTINUE' };
}
},
{
name: 'time-based-access',
evaluate: (context) => {
const { user, action } = context;
const hour = new Date().getHours();
// No financial operations outside business hours for non-admins
if (action.startsWith('payment:') && !user.roles.includes('admin')) {
if (hour < 9 || hour > 17) {
return {
effect: 'DENY',
reason: 'Financial operations only available during business hours'
};
}
}
return { effect: 'CONTINUE' };
}
}
];
// Middleware using policy engine
function abacAuthorize(action, getResource) {
return async (req, res, next) => {
const resource = await getResource(req);
const context = {
user: req.user,
resource,
action,
environment: {
ip: req.ip,
time: new Date(),
method: req.method
}
};
const result = policyEngine.evaluate(context);
if (!result.allowed) {
return res.status(403).json({
error: 'Access denied',
reason: result.reason
});
}
req.resource = resource;
next();
};
}
// Usage
app.put('/orders/:id',
authenticate(),
abacAuthorize('orders:update', async (req) => {
return orderRepository.findById(req.params.id);
}),
updateOrder
);
Service-to-Service Authentication
API Keys for Internal Services
Copy
// middleware/serviceAuth.js
const crypto = require('crypto');
class ServiceAuthenticator {
constructor(options = {}) {
this.services = new Map();
this.algorithm = 'sha256';
}
registerService(serviceId, secret) {
this.services.set(serviceId, secret);
}
generateSignature(serviceId, timestamp, body) {
const secret = this.services.get(serviceId);
if (!secret) {
throw new Error(`Unknown service: ${serviceId}`);
}
const payload = `${serviceId}:${timestamp}:${JSON.stringify(body)}`;
return crypto
.createHmac(this.algorithm, secret)
.update(payload)
.digest('hex');
}
verify(serviceId, timestamp, body, signature) {
// Check timestamp freshness (prevent replay attacks)
const age = Date.now() - parseInt(timestamp);
if (age > 300000) { // 5 minutes
return { valid: false, reason: 'Request expired' };
}
const expectedSignature = this.generateSignature(serviceId, timestamp, body);
const valid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
return { valid, reason: valid ? null : 'Invalid signature' };
}
}
// Middleware
function serviceAuthenticate(authenticator) {
return (req, res, next) => {
const serviceId = req.headers['x-service-id'];
const timestamp = req.headers['x-timestamp'];
const signature = req.headers['x-signature'];
if (!serviceId || !timestamp || !signature) {
return res.status(401).json({ error: 'Missing service authentication' });
}
const result = authenticator.verify(serviceId, timestamp, req.body, signature);
if (!result.valid) {
return res.status(401).json({ error: result.reason });
}
req.service = { id: serviceId };
next();
};
}
// Client-side usage
class ServiceClient {
constructor(serviceId, secret) {
this.serviceId = serviceId;
this.authenticator = new ServiceAuthenticator();
this.authenticator.registerService(serviceId, secret);
}
async request(url, options = {}) {
const timestamp = Date.now().toString();
const body = options.body || {};
const signature = this.authenticator.generateSignature(
this.serviceId,
timestamp,
body
);
return fetch(url, {
...options,
headers: {
...options.headers,
'X-Service-ID': this.serviceId,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
}
}
Mutual TLS (mTLS)
Copy
┌─────────────────────────────────────────────────────────────────────────────┐
│ MUTUAL TLS (mTLS) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Regular TLS: Mutual TLS: │
│ ──────────── ────────────── │
│ │
│ Client Client │
│ │ │ │
│ │──▶ Verify Server Cert │──▶ Verify Server Cert │
│ │ │ │
│ │ (server authenticated) │ (server authenticated) │
│ │ │ │
│ │ │◀── Verify Client Cert │
│ │ │ │
│ ▼ ▼ (BOTH authenticated) │
│ Server Server │
│ │
│ │
│ Certificate Chain: │
│ ───────────────── │
│ │
│ ┌─────────────────────┐ │
│ │ Root CA │ │
│ │ (Trusted by all) │ │
│ └─────────┬───────────┘ │
│ │ │
│ ┌─────────┴───────────┐ │
│ │ Intermediate CA │ │
│ └─────────┬───────────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Order│ │Pay │ │Inv │ │
│ │Cert │ │Cert │ │Cert │ │
│ └─────┘ └─────┘ └─────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
mTLS Configuration
Copy
// security/mtls.js
const https = require('https');
const fs = require('fs');
const tls = require('tls');
class MTLSServer {
constructor(options) {
this.caCert = fs.readFileSync(options.caPath);
this.serverCert = fs.readFileSync(options.certPath);
this.serverKey = fs.readFileSync(options.keyPath);
}
createServer(app) {
return https.createServer({
ca: this.caCert,
cert: this.serverCert,
key: this.serverKey,
requestCert: true, // Request client certificate
rejectUnauthorized: true // Reject invalid certificates
}, app);
}
// Middleware to extract client certificate info
extractClientCert() {
return (req, res, next) => {
const cert = req.socket.getPeerCertificate();
if (!cert || Object.keys(cert).length === 0) {
return res.status(401).json({ error: 'Client certificate required' });
}
req.clientCert = {
subject: cert.subject,
issuer: cert.issuer,
valid_from: cert.valid_from,
valid_to: cert.valid_to,
fingerprint: cert.fingerprint,
serialNumber: cert.serialNumber
};
// Extract service name from certificate CN
req.serviceName = cert.subject.CN;
next();
};
}
}
class MTLSClient {
constructor(options) {
this.agent = new https.Agent({
ca: fs.readFileSync(options.caPath),
cert: fs.readFileSync(options.certPath),
key: fs.readFileSync(options.keyPath),
rejectUnauthorized: true
});
}
async request(url, options = {}) {
return fetch(url, {
...options,
agent: this.agent
});
}
}
// Usage
const mtlsServer = new MTLSServer({
caPath: '/certs/ca.crt',
certPath: '/certs/server.crt',
keyPath: '/certs/server.key'
});
const server = mtlsServer.createServer(app);
app.use(mtlsServer.extractClientCert());
Certificate Generation Script
Copy
#!/bin/bash
# generate-certs.sh
# Create CA
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key -out ca.crt \
-subj "/CN=Microservices CA/O=MyCompany"
# Generate service certificate
generate_service_cert() {
SERVICE=$1
# Generate key
openssl genrsa -out ${SERVICE}.key 2048
# Generate CSR
openssl req -new -key ${SERVICE}.key -out ${SERVICE}.csr \
-subj "/CN=${SERVICE}/O=MyCompany"
# Sign with CA
openssl x509 -req -days 365 -in ${SERVICE}.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out ${SERVICE}.crt
echo "Generated certificates for ${SERVICE}"
}
generate_service_cert "order-service"
generate_service_cert "payment-service"
generate_service_cert "inventory-service"
Secrets Management
HashiCorp Vault Integration
Copy
// security/VaultClient.js
const vault = require('node-vault');
class VaultClient {
constructor(options = {}) {
this.client = vault({
apiVersion: 'v1',
endpoint: options.endpoint || process.env.VAULT_ADDR,
token: options.token || process.env.VAULT_TOKEN
});
this.cache = new Map();
this.leases = new Map();
}
async init() {
// If using Kubernetes auth
if (process.env.KUBERNETES_SERVICE_HOST) {
await this.authenticateKubernetes();
}
}
async authenticateKubernetes() {
const jwt = require('fs').readFileSync(
'/var/run/secrets/kubernetes.io/serviceaccount/token',
'utf8'
);
const result = await this.client.kubernetesLogin({
role: process.env.VAULT_ROLE,
jwt
});
this.client.token = result.auth.client_token;
}
async getSecret(path, key = null) {
// Check cache
const cached = this.cache.get(path);
if (cached && cached.expiry > Date.now()) {
return key ? cached.data[key] : cached.data;
}
try {
const result = await this.client.read(path);
const data = result.data.data || result.data;
// Cache for 5 minutes
this.cache.set(path, {
data,
expiry: Date.now() + 300000
});
return key ? data[key] : data;
} catch (error) {
console.error(`Failed to read secret ${path}:`, error);
throw error;
}
}
async getDatabaseCredentials(role) {
const result = await this.client.read(`database/creds/${role}`);
// Store lease for renewal
this.leases.set(role, {
leaseId: result.lease_id,
leaseDuration: result.lease_duration
});
// Schedule renewal
this.scheduleRenewal(role, result.lease_duration);
return {
username: result.data.username,
password: result.data.password
};
}
scheduleRenewal(role, leaseDuration) {
// Renew at 75% of lease duration
const renewIn = leaseDuration * 0.75 * 1000;
setTimeout(async () => {
const lease = this.leases.get(role);
if (lease) {
try {
const result = await this.client.write('sys/leases/renew', {
lease_id: lease.leaseId
});
this.scheduleRenewal(role, result.lease_duration);
} catch (error) {
console.error(`Failed to renew lease for ${role}:`, error);
}
}
}, renewIn);
}
}
// Usage
const vaultClient = new VaultClient();
await vaultClient.init();
const dbCreds = await vaultClient.getDatabaseCredentials('order-service');
const apiKey = await vaultClient.getSecret('secret/api-keys', 'stripe');
Environment Variable Encryption
Copy
// security/secrets.js
const crypto = require('crypto');
class SecretManager {
constructor(masterKey) {
this.masterKey = Buffer.from(masterKey, 'hex');
}
encrypt(plaintext) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', this.masterKey, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}
decrypt(encrypted) {
const [ivHex, authTagHex, ciphertext] = encrypted.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-gcm', this.masterKey, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Load and decrypt environment variables
loadSecrets(encryptedEnv) {
const secrets = {};
for (const [key, value] of Object.entries(encryptedEnv)) {
if (value.startsWith('ENC:')) {
secrets[key] = this.decrypt(value.substring(4));
} else {
secrets[key] = value;
}
}
return secrets;
}
}
// Kubernetes Secret management
async function loadK8sSecret(secretName, namespace = 'default') {
const k8s = require('@kubernetes/client-node');
const kc = new k8s.KubeConfig();
kc.loadFromCluster();
const coreApi = kc.makeApiClient(k8s.CoreV1Api);
const response = await coreApi.readNamespacedSecret(secretName, namespace);
const secrets = {};
for (const [key, value] of Object.entries(response.body.data)) {
secrets[key] = Buffer.from(value, 'base64').toString('utf8');
}
return secrets;
}
Security Best Practices
Input Validation
Copy
// validation/sanitize.js
const Joi = require('joi');
const xss = require('xss');
// Schema validation
const orderSchema = Joi.object({
customerId: Joi.string().uuid().required(),
items: Joi.array().items(
Joi.object({
productId: Joi.string().uuid().required(),
quantity: Joi.number().integer().min(1).max(100).required()
})
).min(1).max(50).required(),
shippingAddress: Joi.object({
street: Joi.string().max(200).required(),
city: Joi.string().max(100).required(),
country: Joi.string().length(2).required()
}).required()
});
function validateOrder(data) {
const { error, value } = orderSchema.validate(data, {
abortEarly: false,
stripUnknown: true
});
if (error) {
throw new ValidationError(error.details);
}
// Sanitize string fields
value.shippingAddress.street = xss(value.shippingAddress.street);
value.shippingAddress.city = xss(value.shippingAddress.city);
return value;
}
// SQL Injection prevention - use parameterized queries
async function findOrder(orderId) {
// BAD - SQL injection vulnerable
// const result = await db.query(`SELECT * FROM orders WHERE id = '${orderId}'`);
// GOOD - parameterized query
const result = await db.query(
'SELECT * FROM orders WHERE id = $1',
[orderId]
);
return result.rows[0];
}
Rate Limiting
Copy
// middleware/rateLimit.js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
// General API rate limit
const apiLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:api:'
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: {
error: 'Too many requests',
retryAfter: 'Please try again later'
},
standardHeaders: true,
legacyHeaders: false
});
// Stricter limit for auth endpoints
const authLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:auth:'
}),
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 failed attempts
skipSuccessfulRequests: true,
message: {
error: 'Too many login attempts',
retryAfter: 'Account temporarily locked'
}
});
// Apply
app.use('/api/', apiLimiter);
app.use('/auth/login', authLimiter);
Security Headers
Copy
// middleware/security.js
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
},
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: true,
crossOriginResourcePolicy: { policy: 'same-site' },
dnsPrefetchControl: { allow: false },
frameguard: { action: 'deny' },
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
ieNoOpen: true,
noSniff: true,
originAgentCluster: true,
permittedCrossDomainPolicies: { permittedPolicies: 'none' },
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
xssFilter: true
}));
Interview Questions
Q1: How do you secure service-to-service communication?
Q1: How do you secure service-to-service communication?
Answer:Options:
- mTLS (Mutual TLS)
- Both client and server present certificates
- Strongest authentication
- Used in service meshes (Istio, Linkerd)
- API Keys + HMAC
- Service ID + timestamp + signature
- Prevents replay attacks
- Easier to implement than mTLS
- JWT Tokens
- Service-specific JWTs
- Can include permissions
- Short expiry for security
- Use mTLS in production
- API keys for development/simple cases
- Always encrypt in transit (TLS)
Q2: Explain JWT security considerations
Q2: Explain JWT security considerations
Answer:Storage:
- Never in localStorage (XSS vulnerable)
- HttpOnly cookies (preferred)
- Memory only for SPAs
- Short expiry (15-30 minutes)
- Refresh tokens for session management
- Rotate secrets regularly
- Verify signature, issuer, audience
- Check expiry and not-before claims
- Validate against token blacklist
- Use RS256 or ES256 for production
- Never use
alg: none - Specify algorithm in verification
Q3: How do you handle secrets in microservices?
Q3: How do you handle secrets in microservices?
Answer:Never:
- Hardcode in source code
- Commit to version control
- Store in plain environment variables
- Secrets Manager (HashiCorp Vault, AWS Secrets Manager)
- Dynamic secret generation
- Automatic rotation
- Audit logging
- Kubernetes Secrets
- Encrypted at rest
- RBAC for access control
- Mount as files, not env vars
- Encryption
- Encrypt secrets at rest
- Use envelope encryption
- Rotate encryption keys
- Principle of Least Privilege
- Service-specific secrets
- Time-limited access
- Audit all access
Summary
Key Takeaways
- JWT for user authentication
- RBAC/ABAC for authorization
- mTLS for service-to-service auth
- Use secrets managers (Vault)
- Defense in depth approach
Next Steps
In the next chapter, we’ll cover Containerization - Docker best practices for microservices.