Create your own
Lesson illustration

Inter-Service Authentication Strategies

Hello! Welcome back to our course on building production-ready microservices.

In our last lesson, we fortified the "front door" of our system by centralizing JWT validation at the API Gateway. This ensures that only authenticated external users can access our services. However, this raises an important question: what about the communication inside our system? Once a request is past the gateway, should services be able to call each other freely?

In a modern, security-conscious architecture, the answer is no. We operate on a "Zero Trust" principle, meaning we don't implicitly trust any network traffic, even if it originates from within our own cluster. This lesson directly addresses that challenge. Our learning outcome is to compare different strategies for inter-service authentication (e.g., service-to-service JWTs, mTLS). Understanding the trade-offs between these patterns is a hallmark of a senior engineer and a common topic in system design interviews.

Authentication vs. Authorization: A Critical Distinction

Before comparing specific technologies, we must be precise about our terms. It's a distinction that interviewers at top companies will expect you to make clearly.

mTLS vs JWT: What Every Enterprise Developer Should Know

The article "mTLS vs JWT" from Medium offers a sharp clarification of this concept. It sets the foundation for understanding why these two technologies are often used together, not as competitors.

Please read the section "The Authentication vs Authorization Paradigm". Pay close attention to the analogy of the security guard and the access card. This is a powerful way to frame your thinking.

As the article explains:

  • Authentication is about verifying identity (AuthN). It answers the question, "Who are you?" In our context, this means Service-A proving to Service-B that it is genuinely Service-A.
  • Authorization is about verifying permissions (AuthZ). It answers the question, "What are you allowed to do?" This means Service-B checking if Service-A has the right to perform a specific action, like reading inventory data.

With this in mind, let's explore the two main strategies for securing service-to-service communication.

Strategy 1: Service-to-Service JWTs (Application Layer)

This approach leverages the OAuth 2.0 framework you're already familiar with, but for machines instead of users. The specific grant type used here is the Client Credentials Grant.

Here's the flow:

  1. A service (e.g., order-service) acts as a "client" and authenticates itself to an authorization server (like Keycloak) using its unique client_id and client_secret.
  2. The authorization server validates these credentials and issues a short-lived JWT. This token identifies the order-service and may contain claims about its permissions (e.g., scopes: ["inventory:read", "shipping:create"]).
  3. The order-service then includes this JWT in the Authorization header for all its outgoing requests to other services (e.g., inventory-service).
  4. The inventory-service, configured as a resource server, validates the JWT to both authenticate the caller's identity and authorize the requested action based on the token's scopes.

The following video provides a hands-on demonstration of this exact flow using Spring Boot and Keycloak.

Spring Boot 3 Keycloak OAuth 2 Tutorial with Spring Security

The tutorial "Spring Boot 3 Keycloak OAuth 2" by Programming Techie walks through implementing various OAuth 2.0 flows. We will focus on the segments dedicated to the Client Credentials grant, which is designed for machine-to-machine communication.

Please watch the following segments to understand the practical application: Conceptual Overview (51:51 - 54:37): This explains the purpose and simplicity of the Client Credentials flow compared to user-centric flows. Keycloak Configuration (54:37 - 55:57): Observe how a client is configured specifically for this flow. Note the enabling of "Client authentication" and "Service account roles." End-to-End Test (1:03:13 - 1:04:45): This crucial part shows the complete cycle: using Postman to request a token from Keycloak with client credentials, and then using that token to successfully access a secured microservice endpoint.

This pattern is powerful because the JWT can carry rich authorization context. However, it operates at the application layer (L7), meaning your application code is responsible for handling and validating these tokens.

Strategy 2: Mutual TLS (mTLS) (Transport Layer)

Mutual TLS is a more fundamental approach that secures the communication channel itself. You're familiar with standard TLS, which secures your browser's connection to a website (HTTPS). In TLS, your browser (the client) verifies the website's (the server's) certificate.

Mutual TLS (mTLS) extends this by requiring both parties to present and validate each other's certificates. It's a two-way authentication handshake at the transport layer (L4).

TLS vs. mTLS Comparison Table
This table clearly contrasts standard TLS with mTLS. The key difference is the bidirectional authentication in mTLS, where both the client and server need a certificate and must verify each other's identity.

The mTLS Handshake

  1. Service-A wants to call Service-B. It initiates a connection and presents its X.509 certificate.
  2. Service-B verifies that Service-A's certificate is signed by a trusted Certificate Authority (CA).
  3. Service-B then presents its own certificate to Service-A.
  4. Service-A verifies Service-B's certificate against the same trusted CA.
  5. Only after this mutual verification is a secure, encrypted connection established. The application code is unaware this even happened.

The main challenge with mTLS has always been the operational overhead: how do you securely issue, distribute, and rotate certificates for hundreds or thousands of services? This is where a Service Mesh (like Istio or Linkerd) becomes a game-changer.

A service mesh injects a "sidecar" proxy next to each of your microservices. This proxy intercepts all incoming and outgoing traffic. The service mesh's control plane automates the entire certificate lifecycle and enforces that all communication between sidecars uses mTLS.

mTLS in Service Mesh: With and Without Comparison
This diagram illustrates the role of a service mesh. Without mTLS (left), traffic between service proxies is unauthenticated. With mTLS enabled (right), the service mesh automatically ensures that all communication between proxies is encrypted and mutually authenticated, without requiring any changes to your application code.
Test your understanding!

An interviewer asks: "We want to enforce that our payment-service can only be called by the order-service and billing-service. How would you achieve this using mTLS, and how would you achieve it using JWTs?"

Show answer
  • Using mTLS (with a Service Mesh): You would configure a network policy within the service mesh. This policy would state that incoming traffic to the payment-service is only allowed if the mTLS connection originates from a client presenting a certificate with an identity of either order-service or billing-service. This is an infrastructure-level rule that authenticates the caller.

  • Using Service-to-Service JWTs: The payment-service would be configured as a resource server. When it receives a request, it would validate the JWT in the Authorization header. It would check the iss (issuer) and sub (subject) claims of the token to identify which service sent the request. It would then check if the subject is in its allowed list (order-service, billing-service). This is an application-level rule that authenticates the caller.

Both achieve the goal, but mTLS does it transparently at the transport layer, while JWT requires application-level logic.

Comparison and The "Defense in Depth" Strategy

So, which one should you choose? For a senior-level interview, the best answer is rarely "one or the other." It's about understanding the trade-offs and how they can work together.

FeatureService-to-Service JWTs (Client Credentials)Mutual TLS (mTLS)
Primary PurposeAuthentication & AuthorizationStrong Authentication
OSI LayerApplication Layer (L7)Transport Layer (L4)
GranularityFine-grained (scopes, custom claims)Coarse-grained (service identity)
ImplementationIn application code (e.g., Spring Security)In infrastructure (e.g., Service Mesh, Load Balancer)
ProsCarries rich context, flexible, statelessVery high security, application-agnostic, hard to bypass
ConsToken theft risk, application code responsibilityDoesn't carry authz context, complex without a service mesh
"Who are you?"Yes, based on token claims (sub, iss)Yes, based on cryptographic certificate identity
"What can you do?"Yes, based on token scopes (scope)Not directly; only "can you talk to me?"

The most robust, production-grade architectures use both in a defense in depth model.

mTLS vs JWT: What Every Enterprise Developer Should Know

The article "mTLS vs JWT" concludes by advocating for a layered security model. This is the key takeaway for a production-ready mindset.

Please read the sections "The Enterprise Reality: Use Both" and "The 2026 Recommendation". This synthesizes everything we've discussed into a clear, actionable strategy.

The layered strategy works like this:

  1. mTLS secures the pipe: The service mesh ensures that Service-A and Service-B can only communicate if they have mutually authenticated each other's identity via certificates. This prevents any unauthorized service from even opening a connection.
  2. JWT carries the context: When a user request comes through the API Gateway, the gateway can forward the user's JWT downstream. Even though the service-to-service call is authenticated by mTLS, the business logic inside Service-B might still need to know which user initiated the original request to make fine-grained authorization decisions. The JWT provides this context.

Conclusion

You are now equipped to discuss inter-service authentication strategies with nuance and depth. This is a critical aspect of microservice security that separates junior from senior candidates.

Key Takeaways:

  • Inter-service authentication is a cornerstone of a Zero Trust architecture.
  • Service-to-Service JWTs (via Client Credentials) are an excellent application-layer pattern for carrying both authentication and authorization context.
  • mTLS is a transport-layer pattern that provides strong, infrastructure-level authentication, ensuring only trusted services can communicate.
  • The operational complexity of mTLS is largely solved by modern service meshes.
  • The most secure approach is "defense in depth": use mTLS to secure the communication channel and pass JWTs to carry user or service-level authorization context for business logic.

Next Up

We've discussed using client secrets for JWTs and private keys for mTLS certificates. A glaring question remains: where do we store these sensitive values? Checking them into Git is a major security vulnerability. In our next lesson, we will tackle this by exploring strategies for secrets management in a microservices environment, looking at tools like HashiCorp Vault and cloud-native solutions.

Can't find a good explanation? Sign up and we'll make it for you

Sign up