security

Practical Application Security for Developers - Part 7: Securing the Service-to-Service Call Chain

Secure internal call chains with service identity, mTLS, client credentials, signed requests, and layered authorization.

Reading Time: 12 min readAuthor: DeepTechHub
#security#appsec#microservices#mtls#authorization
Practical Application Security for Developers - Part 7: Securing the Service-to-Service Call Chain

Practical Application Security for Developers - Part 7: Securing the Service-to-Service Call Chain

In Part 6 we focused on passwordless user login. This part shifts to a different but equally important identity problem: what happens when there is no user directly on the wire and one service is calling another inside your platform.

That is where many teams become overconfident. Once traffic moves inside a VPC or a Kubernetes cluster, people start treating the network as inherently trustworthy. In practice, this is where lateral movement, misconfigured gateways, over-permissive service accounts, and weak internal authorization policies cause real damage.

This article focuses on how to secure the call chain between services in a way that is practical for backend teams:

  1. How service identity works
  2. When to use OAuth 2.0 client credentials, mTLS, API keys, or signed requests
  3. How to enforce authorization at the application and infrastructure layers
  4. How RBAC, ABAC, and ReBAC apply to service authorization
  5. Which patterns scale without turning into policy chaos

Why Internal Traffic Is Not Automatically Safe

A typical request in a microservice platform rarely ends at one hop. A single user action might trigger a chain like this:

API Gateway -> Orders Service -> Payments Service -> Ledger Service -> Notification Service

At every hop, the receiving service has to answer three questions:

  1. Who is calling me?
  2. Should I trust the proof they presented?
  3. Is this caller allowed to perform this action?

If any service skips those questions because "it came from inside the cluster," the whole chain becomes weaker than its weakest hop.


What Service Identity Actually Means

Service identity is the mechanism that lets one workload prove which service it is. It plays the same role for software that user identity plays for humans.

Without service identity, you cannot do secure:

  • Service-to-service authentication
  • Authorization by caller
  • Auditing by workload
  • Policy enforcement by environment or platform

The Building Blocks

ConceptMeaning
IdentityThe name or principal assigned to a service
AuthenticationThe proof that the caller really holds that identity
AuthorizationThe policy decision about what that identity may do
Trust anchorThe authority that vouches for service identity

Examples of Trust Anchors

  • A certificate authority signing workload certificates
  • An OAuth authorization server issuing access tokens
  • A platform identity system like SPIFFE/SPIRE, cloud workload identity, or a service mesh CA

Without a trusted issuer, a service identity claim is just a string in a header.


Zero Trust in Practice

Zero Trust is often repeated as a slogan, but for service calls it has a concrete meaning:

  • Do not trust traffic because of network location alone
  • Authenticate each caller
  • Authorize each action
  • Reduce implicit permissions
  • Make identity and policy explicit

This does not mean every team needs maximal complexity on day one. It means you should stop using network placement as a substitute for identity.


The Main Patterns for Service Authentication

Most systems use one or more of four patterns.

PatternStrengthBest fit
OAuth 2.0 Client CredentialsStrong service identity with central token issuanceAPIs and platform-integrated services
mTLSStrong transport-level mutual authenticationService mesh, internal east-west traffic
API keysVery simple, low maturitySmall internal tools, short-lived stopgaps
Request signingFine-grained message integrity and replay controlsHigh-integrity APIs, webhook-style verification

The key is not to ask which pattern is universally best. The real question is which layer you want identity to live at and how much operational maturity your platform can support.


OAuth 2.0 Client Credentials for Services

This is often the best place to start when you already have an identity provider.

How It Works

  1. Service authenticates to token endpoint using its own credentials
  2. Authorization server returns an access token
  3. Service attaches token when calling downstream API
  4. Receiving service validates token and applies policy based on claims

Why Teams Like It

  • Centralized identity and token issuance
  • Standard tooling in Spring Security and gateways
  • Tokens can carry audience, scopes, tenant, and caller metadata
  • Easy to audit by issuer and subject

Spring Client Configuration

spring:
  security:
    oauth2:
      client:
        registration:
          payments-client:
            client-id: payments-service
            client-secret: ${PAYMENTS_CLIENT_SECRET}
            authorization-grant-type: client_credentials
            scope: ledger.write
        provider:
          company-idp:
            token-uri: https://id.example.com/oauth2/token

Resource Server Validation in Spring Boot

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://id.example.com/realms/platform

What the Receiving Service Should Check

  • Issuer
  • Audience
  • Expiration
  • Scope
  • Client or subject identity
  • Tenant or environment claims where relevant

Important: A valid token is not automatically an authorized token. Token validation and authorization are separate steps.


mTLS: Identity at the Transport Layer

Mutual TLS solves a slightly different problem. Instead of putting identity mainly inside a bearer token, both sides prove identity during the TLS handshake using certificates.

When mTLS Shines

  • East-west traffic inside Kubernetes
  • Service mesh environments like Istio or Linkerd
  • Environments that want strong workload authentication by default
  • Systems where transport encryption and service identity should be enforced together

What mTLS Gives You

CapabilitymTLS provides it?
Encryption in transitYes
Mutual authenticationYes
Message-level business authorizationNot by itself
User identity propagationNot by itself

That last row matters. mTLS tells you which workload connected. It does not automatically tell you whether the request should be allowed to create an invoice, access a tenant, or impersonate a user.

Practical Architecture

Many mature platforms use:

  • mTLS for workload-to-workload authentication and transport security
  • JWT access tokens for user or service authorization context

Those are complementary, not competing, controls.


API Keys: Simple, Useful, and Easy to Outgrow

API keys remain common because they are quick to implement. One service sends a secret header, the other checks it.

Minimal Example

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class BillingController {
 
    @PostMapping("/process")
    public ResponseEntity<String> process(
            @RequestHeader("X-API-KEY") String apiKey) {
 
        if (!"expected-secret-value".equals(apiKey)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                    .body("Invalid API key");
        }
 
        return ResponseEntity.ok("Processed");
    }
}

Why API Keys Break Down

  • They are static and often forgotten during rotation
  • They do not carry rich claims like scopes or audience
  • They are easy to leak in logs, config files, or repos
  • They are weak for broad platform-wide policy enforcement

Where They Are Still Acceptable

  • Very small internal systems
  • Transitional integrations
  • Low-risk automation inside a tightly controlled environment

Even then, use HTTPS or mTLS and rotate them aggressively.


Signed Requests: Strong Integrity Without Bearer Tokens

Some systems want each request to prove both origin and freshness. Instead of sending a bearer token, the caller signs request data.

This is the pattern used by systems like AWS Signature Version 4 and many webhook verification schemes.

What Usually Gets Signed

  • HTTP method
  • Path
  • Timestamp
  • Selected headers
  • Request body hash

Why This Helps

  • Stops undetected tampering
  • Supports replay protection with timestamp and nonce
  • Binds signature to the exact request, not just to a reusable token

Example Request Signer in Java

import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
 
public class RequestSigner {
 
    private final PrivateKey privateKey;
 
    public RequestSigner(PrivateKey privateKey) {
        this.privateKey = privateKey;
    }
 
    public String sign(String method, String path, String timestamp, String bodyHash)
            throws Exception {
        String canonical = method + "\n" + path + "\n" + timestamp + "\n" + bodyHash;
 
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(canonical.getBytes(StandardCharsets.UTF_8));
 
        return Base64.getEncoder().encodeToString(signature.sign());
    }
}

What the Verifier Must Also Check

  • Signature correctness
  • Timestamp freshness
  • Nonce replay tracking where needed
  • Which public key belongs to which service identity

If you skip freshness checks, a perfectly valid signed request can still be replayed.


Application-Level Authorization

Once a service knows who is calling, it still needs to decide what that caller may do.

At the application layer, that decision is made by the service itself through code, policy libraries, or middleware.

Why Teams Use It

  • Close to business logic
  • Can express resource-specific rules
  • Easy to reason about in the owning service

Why It Fails in Practice

  • Different services implement rules differently
  • One missed check becomes a production gap
  • Policies drift across teams

Example with Spring Security

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class InvoiceController {
 
    @PreAuthorize("hasAuthority('SCOPE_invoices.write')")
    @PostMapping("/invoices")
    public String createInvoice() {
        return "created";
    }
}

This works well when services are disciplined and the security model is simple. It becomes harder once cross-service relationships and tenant-aware rules show up.


Infrastructure-Level Authorization

At the infrastructure layer, the platform enforces identity and coarse-grained policy before traffic reaches the service.

Common Options

Platform componentWhat it can do
Service meshmTLS, workload identity, policy between services
API gatewayToken validation, routing, rate limiting, coarse access control
Ingress / edge proxyExternal auth, request normalization, TLS enforcement

Why This Helps

  • Consistency across services
  • Lower chance of one team forgetting critical checks
  • Central policy visibility

What It Does Not Replace

You still need application-level authorization for business rules like:

  • "Can this service update invoices for tenant X?"
  • "Can this support tool read only redacted customer data?"
  • "Can this workflow execute only during a valid order state transition?"

The strongest designs usually split responsibilities:

  • Infrastructure enforces identity, transport, and broad access boundaries
  • Applications enforce business-specific authorization

RBAC, ABAC, and ReBAC for Service Authorization

These models are often introduced for user access, but they matter for services too.

RBAC: Role-Based Access Control

RBAC asks: does this caller have the required role?

Example:

  • reporting-service has role FINANCE_READER
  • admin-tool has role SUPPORT_OPERATOR

RBAC is easy to understand and implement, but large systems often hit role explosion.

ABAC: Attribute-Based Access Control

ABAC asks: do the attributes of the caller, resource, and environment satisfy policy?

Example service attributes might include:

  • service=payments
  • tenant=eu
  • environment=prod
  • data_classification=restricted

ABAC is flexible and good for dynamic policies, but it depends on high-quality attributes and careful policy design.

ReBAC: Relationship-Based Access Control

ReBAC asks: what is the relationship between the caller and the target resource?

This becomes useful in collaboration-heavy or multi-tenant systems, for example:

  • Service A may access projects owned by the same tenant
  • Service B may update resources delegated by Service C
  • Tooling service may read incidents only for teams it supports

Quick Comparison

ModelBest qualityMain risk
RBACSimplicityRole explosion
ABACFine-grained policyPolicy complexity
ReBACNatural modeling of sharing and delegationRelationship graph complexity

What a Secure Call Chain Usually Looks Like

In a mature platform, a single internal request often uses multiple layers at once:

  1. mTLS authenticates the workload connection
  2. A JWT access token carries service or delegated user context
  3. Gateway or mesh enforces coarse policy
  4. The application enforces fine-grained business authorization
  5. Audit logs record caller identity, tenant, and action

That layered approach is far stronger than betting everything on one mechanism.


Common Mistakes in Service-to-Service Security

  1. Trusting the internal network by default - Private networks reduce exposure, but they are not identity systems.

  2. Using one shared API key for many services - You lose caller attribution and make rotation painful.

  3. Stopping at authentication - Knowing the caller is not the same as authorizing the action.

  4. Skipping audience or scope checks on service tokens - Valid tokens can still be meant for another API.

  5. Letting every service invent its own policy model - Inconsistency becomes a security weakness.

  6. Using mTLS as the only control - Transport identity is necessary, but often not enough for business authorization.

  7. Ignoring replay protection in signed-request systems - Timestamps and nonces are not optional details.


Quick Decision Guide

ScenarioRecommended approach
Small internal tool with two servicesAPI key over HTTPS as a temporary measure
Platform with IdP and multiple APIsOAuth 2.0 Client Credentials
Kubernetes east-west trafficmTLS, ideally via service mesh
High-integrity request verificationSigned requests with replay protection
Rich business authorizationApplication-level policy with RBAC, ABAC, or ReBAC
Large multi-team platformInfra-level identity + app-level business rules

Series Wrap-Up

Across Parts 1 through 7, we moved from core cryptographic primitives to the systems developers use every day:

  • Symmetric cryptography and hashing
  • Public-key cryptography and certificates
  • TLS and secure transport
  • Modern token-based identity
  • OAuth 2.0 and OpenID Connect
  • Passwordless login
  • Service-to-service trust and authorization

The common thread is straightforward: security gets stronger when identity is explicit, cryptography is used for the problem it was designed to solve, and operational shortcuts are treated as temporary rather than architectural truths.

That is the difference between a system that merely has security features and one that is designed to remain trustworthy as it grows.

Continue Learning

Explore more guides and resources to deepen your knowledge.