กลับไปที่บทความ
Authentication Security Microservices Architecture

การยืนยันตัวตนระหว่างเซิร์ฟเวอร์ใน Microservices

พลากร วรมงคล
21 เมษายน 2568 10 นาที

“การเปรียบเทียบในระดับสูงของห้ารูปแบบการยืนยันตัวตนสำหรับการสื่อสารระหว่างเซิร์ฟเวอร์ — mTLS, API Keys & HMAC, OAuth 2.0 Client Credentials, Service Mesh Identity (SPIFFE/SPIRE), และ JWT — พร้อมกับเวลาที่ควรใช้แต่ละรูปแบบ ข้อแลกเปลี่ยน และตัวอย่างในโลกจริง”

Server-to-Server Authentication in Microservices

When services talk to each other, the question isn’t just how they communicate — it’s how they prove who they are. In a microservices architecture, every service call crosses a trust boundary. Without service identity, a compromised internal service can impersonate any other, pivot laterally across your infrastructure, and exfiltrate data silently.

This article compares the five dominant authentication patterns for service-to-service communication in production systems today. For implementation details and code examples, each section links to a dedicated deep-dive.

The Five Patterns at a Glance

PatternIdentity BasisMutual AuthComplexityBest For
Mutual TLS (mTLS)X.509 Certificates✅ YesHighZero-trust networking, regulated industries
API Keys & HMACShared Secret❌ One-wayLowSimple internal APIs, webhook verification
OAuth 2.0 Client CredentialsClient ID + Secret❌ One-wayMediumToken-based access control with scopes
Service Mesh (SPIFFE/SPIRE)Cryptographic SVID✅ YesVery HighLarge-scale microservices, Kubernetes
JWT Service AuthSigned Token❌ One-wayMediumStateless, distributed verification

Why Service Identity Matters

Human authentication (username + password, MFA) has decades of tooling and well-understood patterns. Service authentication is younger, less standardized, and often neglected — which is exactly why it’s a common attack vector.

The threat model is different. Services don’t have keyboards or browsers. They can’t complete CAPTCHA challenges or read SMS codes. Authentication must be automated, cryptographically strong, and resilient to network failures. The key requirements for production service authentication are:

  • Cryptographic strength: Simple shared passwords are insufficient. Credentials should be unforgeable without access to a secret key.
  • Automated lifecycle management: Credentials must be issuable, rotatable, and revocable without human intervention.
  • Mutual verification: Ideally, both sides verify each other — not just the caller proving identity to the callee.
  • Scope limiting: A credential should grant only the minimum permissions needed, so a compromised service can’t access unrelated data.
  • Auditability: Every authentication attempt should be observable and logged.

Mutual TLS (mTLS)

TLS (the protocol behind HTTPS) normally authenticates the server to the client — your browser verifies the server’s certificate. Mutual TLS extends this: both parties present X.509 certificates. The server authenticates to the client and the client authenticates to the server, simultaneously, as part of the TLS handshake.

When to use it: Zero-trust architectures where every connection must be authenticated at the transport layer. Regulated industries (finance, healthcare) with compliance requirements. Any scenario where you want authentication to be transparent to application code — handled entirely by infrastructure.

Strengths: Authentication happens at the transport layer before any application code runs. Mutual — both parties verify each other. No secrets transmitted in HTTP headers. Certificate rotation can be automated with tools like cert-manager (Kubernetes) or Vault. Strong cryptographic identity tied to the service’s workload, not a human-managed password.

Trade-offs: Certificate authority (CA) management is non-trivial. Every service needs a certificate, and certificates expire. Certificate rotation in production without downtime requires careful orchestration. Debugging TLS handshake failures is notoriously painful. Browser-based clients can’t easily present client certificates. Adds latency to connection setup (though TLS session resumption mitigates this for persistent connections).

Real-world examples: Google’s internal BeyondProd architecture mandates mTLS between all services. HashiCorp Vault uses mTLS for its API. Kubernetes control plane components (apiserver, etcd, kubelet) use mTLS for all internal communication.

👉 Deep Dive: Mutual TLS (mTLS) Authentication

API Keys & HMAC Signatures

API key authentication is the simplest pattern: the calling service includes a pre-shared secret key in the request, either as a header (X-API-Key) or query parameter. HMAC (Hash-based Message Authentication Code) extends this: instead of sending the key directly, the caller uses the key to sign a hash of the request, and the server verifies the signature. This is the approach used by AWS Signature Version 4.

When to use it: Internal services where simplicity matters more than mutual authentication. Webhook verification (HMAC signatures verify that webhook payloads came from the expected sender). Third-party integrations where issuing certificates or OAuth flows isn’t practical.

Strengths: Trivial to implement and understand. Widely supported — every HTTP library can add a header. HMAC signatures prevent replay attacks and body tampering. Low infrastructure overhead (no token server, no CA). Easy to audit which service used which key. Key rotation is straightforward.

Trade-offs: API keys are long-lived secrets — if leaked, they provide permanent access until rotated. No built-in expiry (you must build rotation logic). One-way authentication — the client can’t verify the server’s identity. No fine-grained scopes by default (though you can implement them). Keys transmitted in headers are visible in logs if not carefully managed.

Real-world examples: Stripe uses API keys for their entire payment API. GitHub uses HMAC signatures for webhook verification. AWS uses HMAC-based signing (Signature Version 4) for every API request. Twilio, SendGrid, and most webhook-based services use HMAC for payload verification.

👉 Deep Dive: API Keys & HMAC Signatures

OAuth 2.0 Client Credentials Grant

OAuth 2.0 is primarily designed for delegated user authorization, but the Client Credentials Grant flow is specifically designed for machine-to-machine (M2M) authentication with no user involved. A service authenticates to an authorization server using its client ID and secret, receives a short-lived access token (typically a JWT), and presents that token to downstream services.

When to use it: When you need fine-grained, scope-based access control between services. When you already have an identity provider (Auth0, Okta, Keycloak, AWS Cognito) that all services can reference. When you want token-based authentication that’s consistent with how you handle user authentication.

Strengths: Well-standardized (RFC 6749) with broad ecosystem support. Short-lived tokens reduce the blast radius of token leaks. Scopes allow fine-grained permissions per service pair. Tokens can be validated without calling the authorization server (using JWT verification). Integrates naturally with API gateways and service meshes. Works with any identity provider.

Trade-offs: Requires a central authorization server — a new piece of infrastructure with its own availability requirements. Services must handle token caching and refresh logic. Token introspection (for opaque tokens) adds latency. Client secrets still need to be securely distributed to each service. Tokens are bearer tokens — anyone with the token can use it.

Real-world examples: Google Cloud services use OAuth 2.0 service accounts with client credentials for API authentication. GitHub Actions uses OIDC tokens (an extension of client credentials) to authenticate to cloud providers. Auth0 and Okta’s M2M application feature implements client credentials.

👉 Deep Dive: OAuth 2.0 Client Credentials Grant

Service Mesh Identity (SPIFFE/SPIRE)

SPIFFE (Secure Production Identity Framework for Everyone) is an open standard for workload identity in dynamic environments. Instead of static API keys or manually managed certificates, SPIFFE assigns each workload a cryptographically verifiable identity called an SVID (SPIFFE Verifiable Identity Document). SPIRE is the reference implementation that automates SVID issuance and rotation.

In a service mesh (Istio, Linkerd), the sidecar proxy handles SVID attestation and mTLS automatically — application code is completely unaware of authentication. SPIFFE IDs look like URIs: spiffe://trust-domain/service-name.

When to use it: Large Kubernetes-based deployments where managing per-service certificates manually is impractical. When you want zero-trust networking with no changes to application code. When workloads span multiple clouds or clusters and need a unified identity framework.

Strengths: Completely transparent to application code — the sidecar handles everything. Automatic SVID rotation (every hour by default) with zero downtime. Works across clouds, clusters, and bare metal. Cryptographic workload attestation — identity is tied to the process, not a human-managed secret. Integrates with Vault, Kubernetes RBAC, and OPA for policy enforcement.

Trade-offs: Extremely high operational complexity. SPIRE requires its own infrastructure (SPIRE server, agents on every node). Debugging identity issues requires understanding the full SPIFFE/SPIRE stack. Service mesh sidecars add CPU, memory, and latency overhead. Not appropriate for small deployments. Learning curve is steep.

Real-world examples: Uber uses SPIFFE/SPIRE as the identity layer across their entire microservices fleet. Square uses it for workload identity across multi-cloud environments. Istio’s default identity model is based on SPIFFE SVIDs.

👉 Deep Dive: Service Mesh Identity (SPIFFE/SPIRE)

JWT for Service-to-Service Authentication

Services can issue and verify JSON Web Tokens directly without a central authorization server, using asymmetric signing (RS256 or ES256). The calling service signs a JWT with its private key, and the receiving service verifies it using the caller’s public key. This is similar to OAuth 2.0 but without the authorization server in the hot path.

When to use it: When you want stateless, distributed verification without the infrastructure overhead of a full authorization server. When services need to propagate caller identity downstream (token forwarding). When you want human-readable, self-describing tokens with custom claims. Simpler deployments where SPIFFE/SPIRE is overkill.

Strengths: Stateless verification — no central service needed to validate tokens. Asymmetric keys allow any service to verify tokens using only the public key (no secret distribution). Short-lived tokens (minutes) minimize replay window. Custom claims enable rich identity context (service name, version, environment). Widely understood and easy to implement.

Trade-offs: Private key management — each service needs a securely distributed private key. Public key distribution requires a JWKS endpoint or shared configuration. No built-in mutual authentication. Token revocation before expiry requires a blacklist. Clock skew between services causes validation failures if nbf/exp are too tight.

Real-world examples: Internal Google systems use signed JWTs for service identity. AWS Lambda uses signed JWTs in certain inter-service flows. HashiCorp Vault can issue service tokens as JWTs from its PKI secrets engine.

👉 Deep Dive: JWT for Service-to-Service Authentication

Decision Framework

Start with API Keys if you’re building your first microservice and need something working today. Simple, well-understood, and adequate for most internal APIs at small scale. Add HMAC signatures if request integrity matters.

Move to OAuth 2.0 Client Credentials when you need scope-based access control, when you have more than a handful of services, or when you already have an identity provider. This is the sweet spot for most production systems: well-standardized, ecosystem support, and short-lived tokens.

Choose JWT Service Auth when you want stateless distributed verification without a central authorization server. Works well for systems that already use JWTs for user auth and want consistent patterns.

Adopt mTLS when compliance or zero-trust mandates transport-layer authentication, or when your threat model requires mutual authentication on every connection. Consider running it alongside application-layer auth for defense in depth.

Invest in SPIFFE/SPIRE when you operate a large Kubernetes fleet, run workloads across multiple clouds, and have the platform engineering capacity to own the identity infrastructure. Don’t adopt this for small teams — the operational overhead is significant.

Layering Multiple Patterns

Most production systems don’t choose one pattern exclusively. A common production setup:

  • mTLS at the transport layer (enforced by service mesh sidecar) provides mutual network identity
  • OAuth 2.0 Client Credentials tokens in headers provide application-level, scope-based authorization
  • Request signing (HMAC) for external webhooks and third-party integrations
  • JWT propagation to carry caller context across multiple service hops

This defense-in-depth approach means a network-level bypass doesn’t automatically grant application-level access.

Cross-Cutting Concerns

No matter which pattern you choose, these concerns apply:

Credential rotation — Every secret, key, and certificate must have an automated rotation mechanism. Manual rotation is an incident waiting to happen.

Observability — Authentication failures should generate structured logs and metrics. Spike in 401s from a specific service is a security signal.

Least privilege — Credentials should grant the minimum permissions needed. Don’t share credentials across unrelated services.

Secret management — Vault, AWS Secrets Manager, or Kubernetes Secrets (encrypted at rest) for distributing credentials. Never hardcode credentials in source code or container images.

Revocation — Plan for compromised credentials. Can you revoke a service’s identity within minutes of detection? Without automation, the answer is usually no.

The authentication pattern is the immune system of your distributed architecture. Choose based on your threat model, operational maturity, and scale — not on what’s easiest to implement this afternoon.

Comments powered by Giscus are not yet configured. Set PUBLIC_GISCUS_REPO_ID and PUBLIC_GISCUS_CATEGORY_ID in apps/web/.env to enable.

PV

เขียนโดย พลากร วรมงคล

Software Engineer Specialist ประสบการณ์กว่า 20 ปี เขียนเกี่ยวกับ Architecture, Performance และการสร้างระบบ Production

เพิ่มเติมเกี่ยวกับผม

บทความที่เกี่ยวข้อง