Back to Security in Microservices
Q1Question 1 of 4

How do you secure a microservices architecture

Key Concepts
  • Defence in depth: no single security control is enough; layer authentication, authorization, encryption, and secrets management.
  • Authentication with OAuth2 and JWT: a central identity provider issues signed tokens that each service validates locally.
  • Authorization with RBAC: roles ride inside the JWT as claims, and services enforce with @PreAuthorize annotations.
  • The API gateway terminates TLS, validates the JWT once, and applies rate limiting at the edge.
  • Mutual TLS (mTLS) encrypts service-to-service traffic and proves both caller and callee identity.
  • Secrets management via HashiCorp Vault or cloud secrets managers keeps credentials out of code.
  • Zero Trust: no request is trusted just because it came from inside the network; every service authenticates every request.

Definition

Secure microservices with defence in depth: authenticate with OAuth2 and JWT, authorize with RBAC, terminate TLS at the gateway, encrypt service-to-service with mTLS, keep secrets in a vault, and trust nothing merely for being inside the network.
  • Authentication – Establishes who the caller is, typically with OAuth2 and JWT bearer tokens validated locally by each service.
  • Authorization – Decides what the caller may do, using role-based access control (RBAC) with roles carried as JWT claims.
  • API Gateway – The single front door that terminates TLS, validates tokens, enforces rate limits, and routes requests.
  • Mutual TLS (mTLS) – Both caller and callee present certificates so each proves its identity, preventing internal impersonation.
  • Secrets management – Credentials stored encrypted in a system like HashiCorp Vault, rotated automatically, never in code.
  • Zero Trust – No request is trusted merely because it originated inside the perimeter; every service verifies every request.

Imagine National Bank of India's headquarters. At the main entrance a guard checks every visitor's ID badge (authentication at the gateway). Each department door only opens for badges with the right clearance (role-based authorization). All internal mail is in sealed envelopes (TLS and mTLS). The master keys are in a locked cabinet that logs every access (secrets management). And nobody is trusted just because they are inside the building; every door still checks the badge (zero trust).

Understanding the Concepts

How does authentication work with OAuth2 and JWT?

A central identity provider authenticates the user and issues a signed JWT access token. Each service validates the token's signature and expiry locally without calling the identity provider on every request.

JAVA
package com.bank.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // Stateless API, no CSRF needed
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/public/**").permitAll()
                .requestMatchers("/api/v1/transfers/**").hasRole("TELLER")
                .requestMatchers("/api/v1/accounts/**").hasAnyRole("TELLER", "CUSTOMER")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> {}));
        return http.build();
    }
}
TEXT
Authentication flow:
  1. Rajesh logs in -> identity provider verifies credentials
  2. Identity provider issues signed JWT with sub=rajesh, roles=[CUSTOMER]
  3. Rajesh sends JWT in Authorization: Bearer header
  4. Each service validates signature + expiry locally
  5. No server-side session needed -> stateless, scalable

How does RBAC authorization work?

Roles ride inside the JWT as claims. Services enforce permissions with method-level annotations.

JAVA
package com.bank.security;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;

@RestController
@RequestMapping("/api/v1")
public class SecureController {

    @GetMapping("/accounts/{id}")
    @PreAuthorize("hasAnyRole('CUSTOMER', 'TELLER')")
    public String getAccount(@PathVariable String id) {
        return "Account details for " + id;
    }

    @PostMapping("/transfers")
    @PreAuthorize("hasRole('TELLER')")
    public String createTransfer(@RequestBody TransferRequest request) {
        return "Transfer initiated by teller";
    }

    @DeleteMapping("/accounts/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public String closeAccount(@PathVariable String id) {
        return "Account " + id + " closed by admin";
    }

    record TransferRequest(String from, String to, BigDecimal amount) {}
}
TEXT
RBAC enforcement:
  Rajesh (CUSTOMER): GET /accounts/SAV2001 -> 200 OK
  Rajesh (CUSTOMER): POST /transfers -> 403 Forbidden
  Priya (TELLER): POST /transfers -> 200 OK
  Pankaj (ADMIN): DELETE /accounts/SAV2001 -> 200 OK

How does the API gateway provide edge security?

The gateway terminates TLS, validates the JWT, applies rate limiting, and routes to the correct service.

YAML
spring:
  cloud:
    gateway:
      routes:
        - id: account-route
          uri: lb://account-service
          predicates:
            - Path=/api/v1/accounts/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 50
                redis-rate-limiter.burstCapacity: 100
TEXT
Gateway security responsibilities:
  1. Terminate TLS (certificates managed in one place)
  2. Validate JWT once at the edge
  3. Rate limit: 50 req/sec sustained, 100 burst per user
  4. Route to the correct backend service
  5. Strip sensitive headers from external requests

How does mutual TLS (mTLS) protect internal traffic?

With mTLS both the caller and the callee present certificates. A service only accepts calls from peers with valid identities.

YAML
# Istio PeerAuthentication: enforce mTLS cluster-wide
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: banking-prod
spec:
  mtls:
    mode: STRICT
JAVA
package com.bank.security;

public class MtlsExplanation {
    public static void main(String[] args) {
        System.out.println("Without mTLS:");
        System.out.println("  Attacker inside network calls transfer-service directly");
        System.out.println("  Transfer-service accepts: request came from internal IP");
        System.out.println();
        System.out.println("With mTLS:");
        System.out.println("  Attacker has no valid certificate");
        System.out.println("  Transfer-service rejects: certificate validation failed");
        System.out.println("  Only services with mesh-issued certificates can communicate");
    }
}
TEXT
Expected output:
Without mTLS:
  Attacker inside network calls transfer-service directly
  Transfer-service accepts: request came from internal IP

With mTLS:
  Attacker has no valid certificate
  Transfer-service rejects: certificate validation failed
  Only services with mesh-issued certificates can communicate

How does a service mesh enforce security transparently?

A mesh like Istio injects a sidecar proxy that handles mTLS, authorization policies, and observability without changing application code.

YAML
# Istio AuthorizationPolicy: only account-service can call transfer-service
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: transfer-service-policy
  namespace: banking-prod
spec:
  selector:
    matchLabels:
      app: transfer-service
  rules:
    - from:
        - source:
            principals:
              - cluster.local/ns/banking-prod/sa/account-service
      to:
        - operation:
            methods: ["POST"]
            paths: ["/api/v1/transfers/*"]
TEXT
Service mesh security:
  - mTLS automatic between all services
  - Authorization policies restrict which service can call which
  - No application code changes required
  - Observability: every call logged with source/destination identity

How does secrets management work?

Credentials stored in HashiCorp Vault or a cloud secrets manager, accessed with service identity, never in code.

JAVA
package com.bank.security;

public class SecretsManagement {
    // WRONG: secrets in application.yml
    // spring.datasource.password=SuperSecretPassword123
    // -> If repo is leaked, password is exposed

    // CORRECT: secrets from Vault
    // spring.cloud.vault.uri=https://vault.nbi.internal:8200
    // spring.cloud.vault.authentication=KUBERNETES
    // -> Service authenticates with its Kubernetes service account
    // -> Vault returns the DB password, rotated automatically

    public static void main(String[] args) {
        System.out.println("WRONG: password in config file -> leaked if repo exposed");
        System.out.println("CORRECT: password from Vault -> encrypted, access-controlled, rotated");
    }
}
TEXT
Expected output:
WRONG: password in config file -> leaked if repo exposed
CORRECT: password from Vault -> encrypted, access-controlled, rotated

What is Zero Trust?

No request is trusted just because it came from inside the network. Every service authenticates and authorizes every request.

JAVA
package com.bank.security;

public class ZeroTrust {
    public static void main(String[] args) {
        System.out.println("Zero Trust principles:");
        System.out.println("  1. Verify every request (even internal)");
        System.out.println("  2. Encrypt all traffic (mTLS everywhere)");
        System.out.println("  3. Least privilege (each service gets only needed access)");
        System.out.println("  4. Assume breach (monitor, log, alert on anomalies)");
        System.out.println();
        System.out.println("In practice:");
        System.out.println("  Gateway validates JWT at edge");
        System.out.println("  Each service STILL validates JWT (does not trust gateway blindly)");
        System.out.println("  mTLS proves caller identity at transport level");
        System.out.println("  Authorization policy restricts caller-to-callee access");
    }
}
TEXT
Expected output:
Zero Trust principles:
  1. Verify every request (even internal)
  2. Encrypt all traffic (mTLS everywhere)
  3. Least privilege (each service gets only needed access)
  4. Assume breach (monitor, log, alert on anomalies)

What should a senior engineer know?

Validate token issuer AND audience, keep tokens short-lived with refresh rotation, audit authorization decisions centrally, and return consistent 401/403.

TEXT
Senior security checklist:
  [x] OAuth2 + JWT for authentication
  [x] RBAC with roles in JWT claims
  [x] Gateway: TLS termination, JWT validation, rate limiting
  [x] mTLS for all service-to-service traffic
  [x] Service mesh authorization policies
  [x] Secrets in Vault, never in code
  [x] Zero Trust: every service validates every request
  [x] Token: validate iss, aud, exp; short-lived with refresh rotation
  [x] Audit: log every authorization decision centrally

Visual Overview

DIAGRAM
  Microservice Security Architecture:

  ┌─────────────────────────────────────────────────────┐
  │                  Internet                           │
  └──────────────────────┬──────────────────────────────┘
                         │ HTTPS (TLS terminated)
                    ┌────▼────────────┐
                    │   API Gateway   │
                    │ - Validate JWT  │
                    │ - Rate limit    │
                    │ - Route         │
                    └────┬────────────┘
                         │ mTLS
              ┌──────────┼──────────┐
              ▼          ▼          ▼
        ┌──────────┐┌──────────┐┌──────────┐
        │ account  ││ transfer ││ statement│
        │ service  ││ service  ││ service  │
        │          ││          ││          │
        │@PreAuth  ││@PreAuth  ││@PreAuth  │
        │JWT valid ││JWT valid ││JWT valid │
        └────┬─────┘└────┬─────┘└──────────┘
             │           │
             ▼           ▼
        ┌──────────┐┌──────────┐
        │  Vault   ││  Vault   │
        │ (DB pass)││ (API key)│
        └──────────┘└──────────┘

Key Takeaway

Securing a microservices architecture requires defence in depth across multiple layers. Authentication with OAuth2 and JWT establishes the caller's identity through a signed token each service validates locally. Authorization with RBAC maps users to roles carried as JWT claims, enforced with annotations like @PreAuthorize. The API gateway terminates TLS, validates the JWT once, and rate-limits at the edge.

Internally, mutual TLS ensures both caller and callee prove their identity with certificates, so an attacker inside the network cannot impersonate a service. A service mesh like Istio automates mTLS and authorization policies without changing application code. Secrets management via Vault keeps database passwords and API keys out of code, encrypted, access-controlled, and automatically rotated.

At National Bank of India, Pankaj configured the mobile app to call through the gateway, which validated the JWT and rate-limited. Each service still validated the token (zero trust) and ran its own RBAC check: a support agent could view accounts but was denied transfers because the SUPPORT role lacked the TRANSFER permission. When an attacker tried to call the transfer service directly with a forged token, mTLS rejected the connection before the token was even checked.

Remember one sentence: secure microservices with defence in depth: authenticate with OAuth2 and JWT, authorize with RBAC, terminate TLS at the gateway, encrypt service-to-service with mTLS, keep secrets in a vault, and trust nothing merely for being inside the network.

---

Share
Use ← → arrow keys to navigate