Microservices security is not harder than monolith security because microservices are inherently insecure. It is harder because the attack surface multiplies with every service you add. One monolith has one authentication boundary, one network perimeter, one secret store. Ten microservices have ten authentication boundaries, ten internal APIs, ten places where a JWT can be mishandled, and ten services that need to decide whether to trust each other. This guide covers everything that matters in 2026: from JWT and mTLS to Zero Trust, OPA, service mesh, Kubernetes security, and a production-ready architecture you can actually deploy.
I have spent the last decade securing distributed systems — from three-service prototypes to platforms running 200+ microservices handling financial transactions. The security mistakes I see are remarkably consistent across team sizes and company types. This article is the guide I give to senior engineers starting a new microservices project. Not theory. Operational decisions.
1 Why Microservices Security Is Hard
When you break a monolith into services, you do not just split code. You split trust boundaries. And trust boundaries are where security problems live.
In a monolith, when the payment module calls the user module, it is a function call inside a single process. No authentication. No authorisation check. No network hop. The assumption is implicit: if you are inside the process, you are trusted. That assumption is largely safe for a single process behind a perimeter.
In microservices, when the Payment Service calls the User Service, it is a network request that crosses a trust boundary. The User Service cannot assume the caller is legitimate just because it originated from the internal network. The internal network is not a perimeter — it is a shared space that includes every service, every pod, and potentially a compromised process running inside the cluster.
The other challenges compound this. Token propagation: a JWT issued at the gateway must be validated at every downstream service — but propagating it unchanged through multiple hops creates replay attack opportunities. Service impersonation: without mTLS, any process on the internal network can claim to be any service. East-west traffic: internal service-to-service calls are often unmonitored, unencrypted, and unauthenticated. Secret distribution: 10 services need 10 sets of credentials, connection strings, and API keys — multiplying the blast radius of any single secret compromise.
2 Common Security Risks in Microservices
I have audited a lot of microservices architectures. The same risks appear every time — in different configurations but the same underlying patterns.
| Risk | Severity | Root cause | What gets breached |
|---|---|---|---|
| Insecure internal APIs | Critical | Trusting internal network as a perimeter | Any data accessible to any service |
| Service impersonation | Critical | No mTLS, flat internal network | Attacker acts as trusted service |
| JWT token leakage | Critical | Tokens in logs, long expiry, stored insecurely | Account takeover, data access |
| SSRF via service calls | Critical | Services fetch user-supplied URLs internally | Metadata service, internal endpoints |
| Broken authorisation | High | Auth at gateway only, not at service level | Privilege escalation after gateway bypass |
| Over-privileged services | High | Shared admin credentials, no least-privilege | Lateral movement after one service breach |
| Replay attacks | High | Long-lived tokens, no jti tracking | Re-use of intercepted tokens |
| Insecure async messaging | High | Unencrypted Kafka/RabbitMQ topics, no auth | Message injection, data interception |
| Exposed secrets | High | Credentials in config files, env vars, logs | DB access, external API abuse |
| Insecure service discovery | Medium | Service registry with no auth | Attacker registers fake services |
A development service — built fast, with permissive outbound network rules and no mTLS — gets compromised via a dependency vulnerability. The attacker discovers it can make HTTP calls to other internal services with no authentication required. Within minutes it has called the User Service to enumerate accounts and the Payment Service to query transaction history. The gateway never saw a single suspicious request — all the damage happened on the internal network.
Trust internal network traffic by defaultAny service inside the cluster can be compromised
Authenticate only at the API gatewayServices behind the gateway are unprotected from east-west
Use shared database credentials across servicesOne breach gives access to all services' data
Log JWT tokens or API keys in service logsLogs are usually less protected than the secrets themselves
3 Authentication Strategies — When to Use Each
The most common question I get from teams designing a new microservices platform: "What should we use for authentication?" The answer depends on what is authenticating and who it is authenticating to.
| Strategy | Best for | Pros | Cons | Use in 2026 |
|---|---|---|---|---|
| JWT | User → Service, Service → Service (propagation) | Stateless, scalable, self-contained claims | Hard to revoke, payload size grows | ✓ Standard choice for user auth |
| OAuth2 / OIDC | External clients, third-party integrations | Industry standard, delegation model, scopes | Complexity overhead for simple cases | ✓ Enterprise standard |
| API Keys | Machine-to-machine, partner integrations | Simple, long-lived, easy to revoke | No identity assertion, static credentials | ✓ External M2M only |
| mTLS | Service → Service (internal) | Cryptographic identity, no shared secret | Certificate management complexity | ✓ Critical for east-west |
| Session tokens | Single-service web apps | Simple, easy to revoke | Requires centralised session store, not distributed | ✗ Avoid in distributed systems |
The architecture I recommend for most production microservices platforms in 2026:
- North-south (client → services): OAuth2/OIDC at the gateway, issuing JWT access tokens. Clients never call services directly.
- East-west (service → service): mTLS for transport identity, JWT propagation for user context. Services do not re-authenticate users — they forward the original JWT.
- External partners/M2M: API keys with rotation, scoped to specific endpoints, with usage audit logging.
- Kubernetes workloads → cloud APIs: Workload Identity (SPIFFE/SPIRE or cloud provider equivalent). No long-lived API keys.
4 API Gateway Security
The API gateway is your first and most important security layer. It is the single point through which all external traffic enters. Get this right and you eliminate an enormous attack surface before a single request reaches your services.
What the gateway must handle, and what it cannot:
- ✓ Centralised JWT validation: every request validated before reaching services. Invalid tokens rejected at the edge.
- ✓ Rate limiting: per IP, per API key, per user. Multiple layers — global, per-endpoint, per-tier.
- ✓ Request validation: schema validation, content-type enforcement, payload size limits.
- ✓ TLS termination: all external traffic over HTTPS. Internal traffic re-encrypted via mTLS.
- ✗ Per-user authorisation: the gateway does not know if user A is allowed to see user B's orders — that is service-level logic.
- ✗ East-west security: the gateway only sees north-south traffic. Service-to-service calls bypass it entirely.
Spring Cloud Gateway · JWT validation + rate limiting
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
# Validate JWT before forwarding — reject with 401 if invalid
- JwtAuthenticationFilter
# Rate limit: 100 req/min per authenticated user
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 120
key-resolver: "#{@userKeyResolver}"
# Strip sensitive headers before forwarding
- RemoveRequestHeader=Cookie
- RemoveRequestHeader=Authorization # re-inject validated token only
5 JWT Security Best Practices
JWT is everywhere in microservices. That means JWT mistakes are everywhere too. I have reviewed systems where tokens had 30-day expiry, were stored in localStorage, used HS256 with a key that was literally the word "secret", and accepted the alg: none attack. All in the same codebase.
The Non-Negotiable Rules
Java · Spring Boot · correct JWT configuration checklist in code
// 1. Short expiry — max 15 minutes for access tokens
private static final Duration ACCESS_TOKEN_EXPIRY = Duration.ofMinutes(15);
private static final Duration REFRESH_TOKEN_EXPIRY = Duration.ofDays(7);
// 2. RS256 — asymmetric. Sign with private key, verify with public key.
// Every service only needs the public key. Compromise of one service
// does not expose the signing key.
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder
.withPublicKey(rsaPublicKey())
.signatureAlgorithm(SignatureAlgorithm.RS256) // hardcoded, never from token
.build();
}
// 3. Validate iss, aud, exp on EVERY request — not just at login
return Jwts.parserBuilder()
.setSigningKey(rsaPublicKey)
.requireIssuer("https://auth.yourdomain.com")
.requireAudience("https://api.yourdomain.com")
.build()
.parseClaimsJws(token);
// 4. jti (JWT ID) for critical operations — prevents replay
String jti = UUID.randomUUID().toString();
// Store jti in Redis on issue. Reject if already seen on payment/delete ops.
Accept alg: none or trust the token's own alg headerAttacker crafts unsigned tokens for any user
Set access token expiry longer than 15 minutesStolen token is valid for the entire expiry window
Use HS256 with a shared secret in a distributed systemEvery service sharing the secret = multiple compromise points
Store sensitive data (PII, passwords) in JWT payloadJWT payload is Base64-encoded, not encrypted
Token Revocation Strategy
JWT is stateless — there is no built-in revocation. When a user logs out, changes password, or is compromised, you need to invalidate their tokens before expiry. The practical strategies, in order of preference:
- Short expiry (15 min) + refresh token rotation. Best operational choice. The damage window of a stolen access token is maximum 15 minutes. Refresh tokens rotate on every use — a reuse triggers session termination.
- jti blocklist in Redis. On logout or compromise, add the token's
jtito a Redis set with TTL equal to the token's remaining lifetime. Services check the blocklist on each request. Operationally manageable for security-critical operations. - Per-user version counter. Include a
versionclaim in the JWT. Store the current version per user in Redis. On logout, increment the version. Services reject tokens with a version below the current value.
6 Service-to-Service Authentication — mTLS and SPIFFE
This is the section most microservices articles skip. It is also where the most dangerous attack surface in a microservices platform lives.
Most teams secure north-south traffic (client to gateway) carefully. They install TLS certificates, validate JWTs, add rate limiting. And then they leave the internal network completely flat — service A can call service B with no authentication whatsoever because "it is internal." This is wrong. A compromised internal service, a supply chain attack on a dependency, or a container escape gives an attacker unrestricted access to every internal API.
mTLS — Cryptographic Service Identity
Mutual TLS authenticates both sides of every connection. Service A presents a certificate proving it is Service A. Service B presents a certificate proving it is Service B. Neither trusts the other purely because of IP address or network location. A compromised pod impersonating Service A cannot forge its certificate — it will fail the mTLS handshake.
Istio · enable strict mTLS for all services in the mesh
# Apply this PeerAuthentication to your namespace
# STRICT mode: reject any connection that is not mTLS
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT # PERMISSIVE allows plaintext — NEVER use in production
Istio AuthorizationPolicy · service A can only call service B — deny everything else
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: payment-service-policy
namespace: production
spec:
selector:
matchLabels:
app: payment-service
rules:
# Only order-service can call payment-service
- from:
- source:
principals: ["cluster.local/ns/production/sa/order-service"]
to:
- operation:
methods: ["POST"]
paths: ["/api/payments/*"]
# Deny everything else — no other service can call payment
SPIFFE and SPIRE — Workload Identity at Scale
SPIFFE (Secure Production Identity Framework for Everyone) is the standard for workload identity in cloud-native environments. Each workload receives a cryptographically verifiable identity — a SPIFFE ID like spiffe://yourdomain.com/ns/production/sa/order-service. SPIRE is the reference implementation that issues and rotates these identities automatically. Istio and Linkerd both use SPIFFE under the hood for certificate issuance.
mTLS certificates must rotate automatically and frequently — ideally every 24 hours. Manual certificate management at scale is impossible. Service meshes (Istio, Linkerd) handle this automatically. If you are doing mTLS without a mesh, you need an automated certificate rotation solution. A certificate that expires in production because someone forgot to renew it takes down every service that depends on it.
7 Authorisation in Microservices — RBAC, ABAC, and OPA
Authentication answers "who are you?" Authorisation answers "what can you do?" In microservices, authorisation is often either missing entirely (trusting the gateway checked it) or duplicated inconsistently across services (each service implements its own rules differently).
Centralised vs Decentralised Authorisation
| Approach | How it works | Pro | Con |
|---|---|---|---|
| Gateway-only | Auth checked at gateway, services trust anything that passed | Simple | East-west bypass, no per-resource granularity |
| Per-service RBAC | Each service implements its own role checks | Granular | Duplicated logic, inconsistency across services |
| OPA (centralised policy) | Services query OPA for policy decisions | Consistent, auditable, versioned | OPA becomes a dependency, latency overhead |
| JWT claims-based | JWT contains roles/scopes, services enforce locally | No network call, fast | Claims stale if roles change, fat tokens |
Rego · OPA policy — role-based access control across microservices
# opa-policy.rego
package authz
default allow = false
# Admin can perform any operation on any resource
allow {
input.user.role == "ADMIN"
}
# Support can view users but cannot delete or modify
allow {
input.user.role == "SUPPORT"
input.method == "GET"
startswith(input.path, "/api/users")
}
# Users can only access their own profile
allow {
input.user.role == "USER"
input.method == "GET"
input.path == concat("", ["/api/users/", input.user.id])
}
# Payment operations require payment_admin scope + explicit user consent
allow {
input.user.role == "ADMIN"
"payment_admin" in input.user.scopes
input.method == "DELETE"
startswith(input.path, "/api/payments")
}
Java · Spring Boot · query OPA for authorisation decision
@Component
public class OpaAuthorizationClient {
private final WebClient webClient;
public boolean isAllowed(String userId, String role, String method, String path) {
// Query OPA synchronously — consider caching for high-frequency calls
Map<String, Object> input = Map.of(
"user", Map.of("id", userId, "role", role),
"method", method,
"path", path
);
OpaResponse response = webClient.post()
.uri("http://opa-service:8181/v1/data/authz/allow")
.bodyValue(Map.of("input", input))
.retrieve()
.bodyToMono(OpaResponse.class)
.block();
return response != null && Boolean.TRUE.equals(response.getResult());
}
}
8 Secrets Management
Ten microservices means ten sets of database credentials, ten JWT signing keys, ten sets of third-party API keys. Managing this at scale is one of the biggest operational security challenges in a microservices platform. The wrong answer — sharing secrets across services, using the same database credential for everything, or storing them in application.properties — is alarmingly common.
Never use application.properties for production secretsCommitted to git, baked into Docker images, in heap dumps
Never share database credentials across servicesOne service breach gives access to all services' data
Never hardcode JWT signing keysKey rotation requires a code change and full redeployment
Never reuse production secrets in staging or local devDeveloper machines become a production credential compromise vector
The correct production pattern:
- HashiCorp Vault with dynamic secrets: each service gets a unique, short-lived database credential generated on demand. No shared credentials. Automatic rotation.
- AWS Secrets Manager / GCP Secret Manager: for static secrets (API keys, third-party credentials) with managed rotation and IAM-based access control.
- Kubernetes external-secrets operator: syncs secrets from Vault or cloud secret managers into Kubernetes secrets. Services read secrets from Kubernetes — the source of truth is the external store.
For a complete implementation guide with HashiCorp Vault, AWS Secrets Manager, CVE-2026-40982 patch, and Zero Trust RBAC — read our dedicated article: Mastering Secret Management in Spring Boot (2026).
9 Zero Trust Architecture
Zero Trust is not a product. It is not a setting you enable. It is an architectural principle: never trust any traffic automatically, regardless of where it comes from. Internal traffic, external traffic, traffic from a known IP — all of it must prove identity and authorisation on every request.
The mental model shift required is significant. In a traditional perimeter model, you trust internal traffic because it passed through the perimeter. In Zero Trust, the perimeter does not exist as a concept. Every service is equally untrusted until it proves otherwise on every request.
The four pillars of Zero Trust in microservices:
- Verify every request: JWT validation at every service, not only at the gateway. mTLS for every service-to-service connection.
- Identity-based communication: services identify by certificate (mTLS/SPIFFE), not by IP address. IP addresses are not identities — they are transient infrastructure attributes.
- Least privilege: each service has access only to the resources it needs for its specific function. Order Service cannot call Payment Service's admin API. Payment Service cannot read User Service's internal admin data.
- Continuous verification: tokens expire. Certificates rotate. Policies are re-evaluated. Access is not granted once and maintained indefinitely — it is continuously re-proven.
10 Secure Communication — TLS 1.3 and Encrypt East-West
The instinct is to encrypt north-south traffic (external) and trust that east-west (internal) is safe because "it never leaves the cluster." This instinct is wrong. An attacker who compromises a single pod is inside the cluster. An insider threat is inside the cluster. A misconfigured network policy can route internal traffic outside the cluster. East-west traffic must be encrypted.
application.yml · enforce TLS 1.3, disable older versions
server:
ssl:
enabled: true
protocol: TLS
enabled-protocols: TLSv1.3 # only TLS 1.3 — disable 1.1 and 1.2
ciphers:
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
- TLS_AES_128_GCM_SHA256
key-store: ${SSL_KEYSTORE_PATH}
key-store-password: ${SSL_KEYSTORE_PASSWORD}
key-store-type: PKCS12
Kubernetes NetworkPolicy · restrict east-west traffic — deny by default
# Default deny all ingress for a namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {} # applies to all pods
policyTypes:
- Ingress
- Egress
---
# Allow payment-service to receive from order-service only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-order-to-payment
namespace: production
spec:
podSelector:
matchLabels:
app: payment-service
ingress:
- from:
- podSelector:
matchLabels:
app: order-service
ports:
- protocol: TCP
port: 8443
11 Kubernetes Security
If you are running microservices in 2026, you are probably running them in Kubernetes. The platform introduces its own security surface — and its defaults are not production-safe.
Kubernetes · pod security context — non-root, read-only filesystem
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
# Non-root user for all containers
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: order-service
image: your-registry/order-service:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true # container cannot write to its own filesystem
capabilities:
drop:
- ALL # drop all Linux capabilities
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "100m"
Critical Kubernetes security checklist:
- RBAC for service accounts: each service runs as a dedicated service account with permissions scoped to exactly what it needs — no admin cluster roles for application pods.
- Network policies: default-deny all, then explicitly allow only required service-to-service communication. Pods should not be able to reach services they have no business calling.
- Pod Security Standards: enforce
Restrictedprofile — no privileged pods, no hostNetwork, no hostPID, no root containers. - Service mesh (Istio or Linkerd): automatic mTLS, traffic policy enforcement, observability without application code changes.
- Image scanning: scan all container images before deployment. Trivy, Snyk, or similar. Block images with HIGH/CRITICAL CVEs from reaching production.
12 Logging, Monitoring & Incident Response
Security without visibility is hope. A breach you cannot detect is a breach you cannot contain. Most microservices platforms I audit have excellent application logs — and almost no security event monitoring.
Java · structured security audit logging with trace correlation
// Every security event should be a structured log entry
@Component
public class SecurityAuditLogger {
private static final Logger log = LoggerFactory.getLogger("SECURITY_AUDIT");
public void logAuthFailure(String userId, String endpoint, String reason, String ip) {
log.warn("""
{
"event": "AUTH_FAILURE",
"userId": "{}",
"endpoint": "{}",
"reason": "{}",
"sourceIp": "{}",
"traceId": "{}",
"timestamp": "{}"
}
""",
userId, endpoint, reason, ip,
MDC.get("traceId"),
Instant.now());
}
// Log: AUTH_FAILURE, ACCESS_DENIED, RATE_LIMITED,
// SUSPICIOUS_TOKEN, SERVICE_CALL_REJECTED
// NEVER log: JWT tokens, passwords, API keys, PII
}
What to monitor and alert on:
- 401/403 spike per service: sudden increase signals credential stuffing, token theft, or misconfiguration.
- 429 rate limit hits per user: sustained 429s from one identity may indicate automated abuse.
- mTLS handshake failures: unexpected certificate rejections signal service impersonation attempts or misconfigured deployments.
- Unusual service call patterns: a service calling another service it has never called before, or at 10x its normal rate.
- Secret access patterns: Vault and Secrets Manager audit logs should feed your SIEM — unusual secret access outside normal deployment windows is a high-fidelity alert.
OpenTelemetry trace IDs propagated through every service call mean you can reconstruct the exact path of any request — including a malicious one. When a security incident is detected, the trace ID tells you exactly which services were called, in what order, with what parameters. Without distributed tracing, incident reconstruction in a 20-service architecture is guesswork. With it, it is a query.
13 Security Testing
Security testing cannot be a pre-release gate. By the time it runs before release, the vulnerability has already been in the codebase for weeks. Security testing belongs in your CI pipeline, on every pull request, as a blocker — not a checkbox.
GitHub Actions · security testing pipeline on every PR
name: Security Pipeline
on: [pull_request]
jobs:
sast:
name: Static Analysis (Semgrep)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
run: semgrep --config p/java --config p/spring --error
# Fails build if HIGH severity issues found
dependency-scan:
name: Dependency Vulnerabilities
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: OWASP Dependency Check
run: mvn verify -Powasp-check
# Fails build on CVSS >= 7.0
container-scan:
name: Container Image Scan
runs-on: ubuntu-latest
steps:
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Trivy scan
run: |
trivy image --exit-code 1 --severity HIGH,CRITICAL app:${{ github.sha }}
secret-scan:
name: Secret Detection
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Gitleaks
run: gitleaks detect --source . --exit-code 1
14 Real-World Production Security Architecture
Here is what a production-grade microservices security architecture looks like end-to-end. Not a diagram from a textbook — the architecture I would deploy today for a platform handling financial transactions.
15 Common Mistakes — What I Find in Production
These are the patterns I encounter most consistently when I audit microservices architectures. Each one looked like a reasonable shortcut during development and became a security liability in production.
16 Recommended Security Stack for 2026
This is the stack I would select today for a new microservices platform handling sensitive data. Every choice is based on production experience, not benchmarks.
| Layer | Recommended tool(s) | Why |
|---|---|---|
| WAF / CDN | Cloudflare · AWS WAF + CloudFront | Managed DDoS, SQL injection, XSS protection at the edge |
| API Gateway | Kong · Spring Cloud Gateway · Envoy | Centralised JWT validation, rate limiting, routing, TLS termination |
| Auth / Identity | Keycloak · Auth0 · custom Spring Boot | OAuth2/OIDC, JWT RS256, MFA, SSO, token rotation |
| Service mesh / mTLS | Istio · Linkerd | Automatic mTLS, SPIFFE identity, AuthorizationPolicy per service |
| Authorisation | Open Policy Agent (OPA) | Centralised policy, Rego language, versioned in Git, decoupled from code |
| Secrets management | HashiCorp Vault · AWS Secrets Manager | Dynamic credentials, automatic rotation, Zero Trust RBAC, audit log |
| Secret scanning | Gitleaks · Trufflehog | Pre-commit + CI secret detection — prevents credentials reaching git |
| SAST | Semgrep · SonarQube | Static analysis on every PR — catches injection, insecure patterns, AI-generated bugs |
| Dependency scanning | OWASP Dependency-Check · Snyk · Trivy | Blocks deployment of services with known HIGH/CRITICAL CVEs |
| Container security | Trivy · Falco · Pod Security Standards | Image scanning, runtime threat detection, no-root pods |
| Observability / SIEM | OpenTelemetry · ELK · Grafana · Datadog | Distributed tracing, security event correlation, audit trails |
| DAST / Pentesting | OWASP ZAP · Burp Suite · scheduled pentests | Dynamic testing against running services — quarterly minimum |
"Microservices security is not about tools. It is about the discipline to apply the right check at every trust boundary — every time, consistently, across every service your team ships."
FAQ — Microservices Security 2026
A monolith has one authentication boundary, one network perimeter, and one secret store. Microservices multiply all three with every service added. Each service-to-service call crosses a trust boundary that must be authenticated. JWT tokens must be validated independently by each service. Secrets must be distributed securely to each service. East-west traffic (internal) is as dangerous as north-south (external) because a compromised internal service can call any other service that trusts the internal network. The blast radius of any single compromise scales with the number of services.
JWT is the standard for microservices. It is stateless — each service verifies the token independently without querying a shared session store. Session tokens require a centralised store that every service must call, which adds latency and creates a single point of failure. JWT scales horizontally by design. The main trade-off is revocation: JWT is valid until expiry. Mitigate with short expiry (15 minutes), refresh token rotation, and a jti blocklist in Redis for high-security operations like password reset and payment.
Mutual TLS authenticates both sides of every connection with certificates. In microservices, mTLS gives each service a cryptographic identity — Service A proves it is Service A when calling Service B, and Service B proves it is Service B in return. Without mTLS, any process on the internal network can call any internal service with no identity proof. Service meshes like Istio and Linkerd automate mTLS certificate issuance and rotation transparently, so services do not need to manage certificates in application code.
Zero Trust means no traffic is trusted by default — internal or external. Every request must prove identity and authorisation regardless of its source. A service-to-service call inside the cluster must authenticate via mTLS and be authorised by RBAC or OPA policy, just as a client request from the internet must. Zero Trust eliminates the assumption that internal network traffic is safe — an assumption that is exploited whenever an internal service is compromised.
Open Policy Agent is a general-purpose policy engine that decouples authorisation logic from application code. Services query OPA for policy decisions instead of each implementing its own RBAC rules. Policies are written in Rego, versioned in Git, and deployed independently of service code. Use OPA when your authorisation logic is complex, when you need consistent enforcement across multiple services, or when you need to audit and version access control rules separately from application deployments.
Security Is Not a Service — It Is an Architecture Decision
The mistake I see most often in microservices security is treating it as a layer you add after the architecture is settled. Security added after the fact is security fighting the architecture. The services that trust internal traffic by default, the JWTs with 24-hour expiry, the shared database credentials — these are architectural decisions that make security harder to retrofit than to design in.
The platform that gets this right shares a few consistent properties:
- Every trust boundary has an explicit security control. mTLS, JWT validation, OPA policy — applied at every hop, not just at the perimeter.
- Each service has minimum privilege. Its own database credentials, its own secret scope, access only to the APIs it specifically needs.
- Secrets are dynamic. Vault-issued credentials that expire in hours. No standing credentials. No shared credentials.
- Security testing is in CI. Every PR. Not a release gate. Continuous.
- Observability is security-aware. Security events are structured, correlated, and alerting on real signals — not noise.
None of this is exotic. Every tool in the stack above is mature, production-proven, and well-documented. The missing ingredient in most organisations is not tooling — it is the discipline to apply these controls consistently across every service, every environment, and every sprint. — Priya
Test Your API Security Posture Right Now
Verify JWT validation, check CORS headers, test rate limiting, and inspect your error responses — all in the browser.