🏗️ Section 17 · Question #23

How to implement authentication and authorization in microservices

In distributed microservices, application security is partitioned into two fundamental responsibilities:


🟢 Junior Level

In distributed microservices, application security is partitioned into two fundamental responsibilities:

  • Authentication (AuthN) — “Who are you?”: Verifies the identity of a client or user (e.g., username/password credentials, multi-factor OTP, or SSO identity assertions).
  • Authorization (AuthZ) — “What can you do?”: Determines whether an authenticated identity has permission to perform a specific action (e.g., checking ROLE_ADMIN, scope permissions orders:write, or document ownership).
                      ┌───────────────────────────────────────────────┐
                      │    Centralized Identity Provider (IdP)        │
                      │               (Keycloak / Okta)               │
                      └───────────────────────▲───────────────────────┘
                                              │ 1. Login / OAuth2 PKCE
                                              ▼ 2. Return Signed JWT
[SPA / Mobile Client] ──3. Request + Bearer JWT──► [API Gateway]
                                                         │ 4. Verify Signature (JWKS)
                                                         ▼ 5. Propagate: X-User-Id: 42
                                                   [Order Service]

The Industry Standard: OAuth 2.0 + OpenID Connect (OIDC) + JWT

  1. Centralized Identity Provider (IdP): Endpoints do not manage passwords independently. Authentication is centralized in an IdP like Keycloak, Auth0, or Okta.
  2. JSON Web Token (JWT): Upon successful authentication, the IdP issues a cryptographically signed JWT Access Token containing claims (sub, roles, exp) and a digital signature.
  3. Bearer Token Transmission: The client transmits the token with every request via the standard HTTP header: Authorization: Bearer <JWT-token>
  4. API Gateway Ingress: The API Gateway validates token syntax, cryptographic validity, and expiration at the perimeter before routing traffic downstream.

🟡 Middle Level

1. User Context Propagation Patterns

Pattern Mechanism Advantages Trade-offs & Risks
Edge Authentication + Headers Gateway verifies the JWT, strips raw client headers, and forwards internal headers (X-User-Id, X-User-Roles). Downstream microservices remain lightweight with zero cryptographic libraries. 🔴 Vulnerable to header spoofing inside perimeter if network lacks Zero Trust isolation.
Token Relay Gateway passes the original Bearer JWT unmodified down through all inter-service hops. Maximum auditability: every downstream service sees the original cryptographic token. Higher CPU overhead from redundant cryptographic signature verification across every microservice.
Phantom Token Pattern Client receives an opaque reference token (UUID); Gateway exchanges it for a signed internal JWT. Eliminates exposure of internal claims, roles, or tenant IDs to external mobile/SPA clients. Requires high-speed in-memory cache lookup (Redis) at the API Gateway layer.

2. Spring Security 6 Resource Server Configuration (Spring Boot 3)

Downstream microservices are configured as OAuth2 Resource Servers that validate incoming JWTs using public cryptographic keys downloaded from the IdP’s JWKS (JSON Web Key Set) endpoint:

# application.yml: Spring Security OAuth2 Resource Server
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          # URL where the microservice fetches public keys to verify digital signatures
          jwk-set-uri: http://keycloak:8080/realms/production/protocol/openid-connect/certs
          issuer-uri: http://keycloak:8080/realms/production
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Activates @PreAuthorize method-level security
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/public/*").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/v1/orders/*").hasAuthority("SCOPE_orders.write")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
            .build();
    }
}

// Method-level authorization with Spring Expression Language (SpEL):
@Service
public class OrderService {

    @PreAuthorize("hasRole('ADMIN') or #userId == authentication.name")
    public OrderDto getOrderForUser(Long orderId, String userId) {
        // Enforces that only an administrator or the specific order owner can read this order
        return orderRepository.findOrder(orderId);
    }
}

🔴 Senior Level

1. Inter-Service Security: Zero Trust and mTLS

Under the Zero Trust Architecture, the internal datacenter or Kubernetes pod network is considered inherently hostile. A vulnerability in a single public-facing container must never allow an attacker to send forged X-User-Roles: ROLE_ADMIN headers to internal databases or billing services.

[Order Service Pod]                             [Billing Service Pod]
┌────────────────────────┐                      ┌────────────────────────┐
│ [Java Application]     │                      │ [Java Application]     │
│           │            │                      │           ▲            │
│  (Plaintext Localhost) │                      │  (Plaintext Localhost) │
│           ▼            │                      │           │            │
│ [Envoy Sidecar Proxy]  │ ──(mTLS Encrypted)─► │ [Envoy Sidecar Proxy]  │
│ (SPIFFE ID Identity)   │                      │ (Validates Cert & SAN) │
└────────────────────────┘                      └────────────────────────┘
  1. Service-to-Service AuthN (mTLS): Enforced via a Service Mesh (Istio / Linkerd). Envoy sidecars establish encrypted Mutual TLS connections, authenticating identities using short-lived x509 SPIFFE certificates (spiffe://cluster/ns/prod/sa/order-service-sa).
  2. Service-to-Service AuthZ (Machine-to-Machine): Background asynchronous jobs use the OAuth 2.0 Client Credentials Grant. The worker microservice authenticates with Keycloak using its own client_id and client_secret to obtain a dedicated service token.

2. The Stateless JWT Revocation Dilemma

Because JWTs are self-contained and stateless, a compromised token remains valid until its exp claim expires. If an employee is terminated or a laptop is stolen, an attacker can continue making API calls until expiration:

The Enterprise Solution:

  1. Short-Lived Access Tokens (5–15 Minutes): Limits the vulnerability window of stolen tokens to minutes.
  2. Refresh Token Rotation (RTR): Clients refresh access tokens using long-lived refresh tokens stored securely in Keycloak. When a refresh token is used, Keycloak issues a new token pair and invalidates the old refresh token. Compromised accounts can be immediately locked by invalidating their refresh token in Keycloak.
  3. Distributed Token Blocklist in Redis: For instant revocation (e.g., explicit user logout), the API Gateway records the token’s unique identifier (jti — JWT ID) into a Redis cache with a TTL equal to the token’s remaining lifespan. The Gateway checks Redis for incoming requests, dropping blacklisted tokens immediately.

3. Confused Deputy Attacks & Token Exchange (RFC 8693)

When Service A calls Service B on behalf of a user, and Service B subsequently calls Service C:

  • If Service B forwards the user’s raw client token to Service C, Service B can act as a Confused Deputy, reusing that broad client token to call unauthorized third-party services on behalf of the customer.
  • The Solution: OAuth 2.0 Token Exchange (RFC 8693). Service B exchanges the user’s token at the IdP for a downscoped token minted specifically for Service C, containing an explicit audience (aud: service-c) and restricted scopes.

4 Tricky Questions

1. Why is trusting unverified X-User-Id headers inside an internal microservices network a critical vulnerability?

Answer: If internal microservices blindly trust X-User-Id: 1 and X-User-Roles: ROLE_ADMIN under the assumption that the API Gateway filtered them: Any internal compromise (such as a remote code execution exploit like Log4Shell, an SSRF vulnerability, or an infected open-source dependency) allows an attacker inside the pod network to craft direct HTTP requests to other internal services with arbitrary forged admin headers, bypassing the API Gateway entirely. Defenses:

  1. The API Gateway signs an internal, short-lived JWT token passed downstream.
  2. Enforce Kubernetes NetworkPolicies and Istio AuthorizationPolicies so that internal services reject any direct TCP connections originating from unauthorized pods or bypassing the gateway.

2. How do microservices verify JWT signatures without incurring a network round-trip to Keycloak on every request?

Answer: Through Asymmetric Cryptography (RS256 / ES256) and the JWKS (JSON Web Key Set) protocol:

  1. The Identity Provider signs the JWT using its private cryptographic key, which is kept strictly secret.
  2. When a microservice boots up, it issues a single HTTP request to /.well-known/jwks.json on the IdP to download the IdP’s public keys and caches them in local JVM memory.
  3. For every incoming request, the microservice mathematically validates the signature using the cached in-memory public key in under 0.1 milliseconds with zero network I/O.
  4. If the IdP rotates its keys, the incoming token includes a new kid (Key ID) header; the microservice detects the unknown kid, refreshes its JWKS cache over the network once, and resumes local verification.

3. What is the fundamental difference between RBAC, ABAC, and ReBAC in microservices authorization?

Answer:

  • RBAC (Role-Based Access Control): Permissions are assigned to static roles (ROLE_MANAGER, ROLE_CUSTOMER). Coarse-grained and easy to implement, but rigid: cannot express fine-grained rules like “A manager can only view orders in their specific department.”
  • ABAC (Attribute-Based Access Control): Evaluates boolean policies dynamically based on attributes of the user, resource, and environment (e.g., user.department == order.department AND time >= 09:00). Implemented via policy engines like Open Policy Agent (OPA).
  • ReBAC (Relationship-Based Access Control): Permissions are computed from graph relationships between entities (based on Google Zanzibar / Ory Keto). For example: “A user can edit an invoice if they are a member of an organization that owns the project linked to that invoice.”

4. Where should Single Page Applications (SPAs) store Refresh Tokens to prevent theft via Cross-Site Scripting (XSS)?

Answer: Storing tokens in browser localStorage or sessionStorage is fundamentally insecure: any third-party script injected via an XSS vulnerability has unrestricted read access to JS storage and can exfiltrate tokens. The Secure Architectural Solution: Store the Refresh Token in an HttpOnly, Secure, SameSite=Strict Cookie.

  • The HttpOnly flag prevents JavaScript from accessing the cookie via document.cookie, completely neutralizing XSS token theft.
  • The Secure flag guarantees transmission only over HTTPS.
  • The SameSite=Strict flag and Double-Submit Cookie tokens protect against Cross-Site Request Forgery (CSRF).

🎯 Interview Cheat Sheet

Must Know:

  • AuthN vs AuthZ: Authentication verifies identity (“Who are you?”); Authorization checks permissions (“What can you do?”).
  • Core Standard: OAuth 2.0 + OpenID Connect (OIDC) with a centralized IdP (Keycloak, Auth0).
  • Format: JWT with asymmetric digital signatures (RS256/ES256); verified locally in-memory via cached JWKS public keys.
  • Stateless Revocation: Mitigate unrevokable JWTs using short 5–15 min lifespans + Refresh Token Rotation + Redis blacklists for instant logout.
  • Zero Trust: Never trust internal network traffic; protect service-to-service calls with mTLS via Service Mesh (Istio) and OAuth2 Client Credentials.
  • Fine-Grained AuthZ: Use Spring Security @PreAuthorize SpEL expressions for RBAC, and Open Policy Agent (OPA) for complex dynamic ABAC.

Common Follow-up Questions:

  • “What is PKCE in OAuth 2.0?” → Proof Key for Code Exchange (RFC 7636); an extension for mobile apps and SPAs that prevents authorization code interception attacks without requiring a hardcoded client secret.
  • “Why are JWTs not encrypted by default?” → Standard JWTs (JWS) are digitally signed and Base64Url-encoded, meaning their payload is publicly readable. If confidential data must be included, use JWE (JSON Web Encryption).

Red Flags (DO NOT Say):

  • ❌ “JWT tokens are encrypted, so you can safely store plain-text passwords and credit cards inside them.” (JWTs are signed, not encrypted; anyone can decode the payload).
  • ❌ “Every microservice should execute an HTTP request to Keycloak on every incoming request to validate the token.” (Turns the IdP into a severe single point of failure; verify signatures locally via JWKS).
  • ❌ “We don’t need authentication between internal microservices because they are protected by our cloud firewall.” (Violates Zero Trust; allows catastrophic lateral movement if any pod is breached).