JustAppSec
Back to guides

Service-to-Service Authentication Best Practices

When services talk to each other, they need to prove identity. This guide covers the three main approaches, when to use each, and how to avoid common mistakes.

The Three Options

ApproachIdentity ProofWorks Across NetworksComplexity
mTLSX.509 certificatesYesHigh (cert management)
JWT (signed)Signed token with claimsYesMedium
OAuth 2.0 Client CredentialsAccess token from auth serverYesMedium-High

Mutual TLS (mTLS)

Both client and server present certificates and verify each other. The TLS handshake establishes identity before any application data flows.

When to use:

  • Zero-trust networks where every connection must be verified.
  • Service meshes (Istio, Linkerd handle mTLS automatically).
  • High-security environments (financial, healthcare).

How it works:

  1. Each service has a certificate signed by an internal Certificate Authority (CA).
  2. On connection, both sides present their cert.
  3. Both sides verify the cert against the CA.

Kubernetes + Istio example:

Istio automatically provisions and rotates mTLS certificates for all services in the mesh:

# PeerAuthentication — enforce mTLS
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT

Reference: Istio — Mutual TLS

Without a service mesh:

Use your platform's certificate management:

Certificate rotation: automate this. Manual rotation breaks. Use short-lived certs (hours to days) whenever possible.

JWT-Based Service Auth

Services authenticate by presenting a signed JWT. The receiving service validates the signature and claims.

When to use:

  • Services already have a JWT infrastructure.
  • You need to pass claims (roles, scopes, tenant ID) between services.
  • Simpler setup than mTLS in environments without a service mesh.

Implementation:

import jwt from "jsonwebtoken";

// Service A — create a service token
const serviceToken = jwt.sign(
  {
    sub: "order-service",
    aud: "inventory-service",
    scope: "inventory:read",
  },
  PRIVATE_KEY,
  { algorithm: "RS256", expiresIn: "5m" }
);

// Service B — verify the token
const payload = jwt.verify(serviceToken, PUBLIC_KEY, {
  algorithms: ["RS256"],
  audience: "inventory-service",
});

Key considerations:

  • Use asymmetric keys (RS256 or ES256) so services only need the public key to verify.
  • Set short expiry (5 minutes or less).
  • Validate aud to ensure the token was meant for this service.
  • Validate iss to ensure it came from a trusted service.

OAuth 2.0 Client Credentials

Each service is an OAuth client. Services obtain an access token from an authorization server using their client credentials, then present that token to other services.

When to use:

  • You already have an OAuth infrastructure (Auth0, Keycloak, Entra ID).
  • You want centralized token issuance and revocation.
  • You need fine-grained scopes managed centrally.

Flow:

Service A → Auth Server: POST /oauth/token
  grant_type=client_credentials
  client_id=order-service
  client_secret=...
  scope=inventory:read

Auth Server → Service A: { access_token: "...", expires_in: 300 }

Service A → Service B: GET /api/stock
  Authorization: Bearer <access_token>

Service B: validates token (via introspection or local JWKS verification)

Provider examples:

Comparison and Decision Framework

FactormTLSJWTOAuth Client Credentials
Setup complexityHighLow-MediumMedium
Certificate/key managementRequired (X.509)Required (signing keys)Managed by auth server
Claim-based access controlNo (identity only)YesYes
Revocation speedPotentially slow (CRL/OCSP often delayed)Delayed (until expiry)Fast (via auth server)
Works without auth serverYesYesNo
Service mesh compatibleNativeExtra stepExtra step

Choose mTLS when:

  • You run a service mesh or zero-trust network.
  • You need transport-layer identity, not just application-layer.

Choose JWT when:

  • You want lightweight, self-contained tokens with claims.
  • Services are in a trusted network and you do not need mTLS overhead.

Choose OAuth Client Credentials when:

  • You want a centralized authority for token issuance and revocation.
  • You are already using an identity provider.

Anti-Patterns

  • Shared static API key between services. No identity, no rotation, no scoping.
  • IP-based trust only. Breaks in dynamic environments (containers, auto-scaling).
  • No expiry on service tokens. A leaked token is valid forever.
  • Not validating aud on JWTs. A token meant for Service A works on Service B.
  • mTLS with a single CA and no service identity checks. Any service with a cert can talk to any other service.

Related Guides


Content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly, send corrections to [email protected]. Always consult official documentation and validate key implementation decisions before making design or security choices.

Need help?Get in touch.