When you build a microservices backend, most security tutorials cover one thing well: securing the public gateway. Validate the JWT, extract the user identity, reject bad requests.
What they don't cover is what happens after the gateway.
I hit this problem while building a productivity app with four microservices — Gateway, Auth, User, and Core — each with their own database.
The Problem
Core Service needed user data from User Service. Simple enough. But the JWT was already consumed at the gateway. And my scheduled jobs had no HTTP request context at all.
Three options came to mind. All had real problems.
Option 1 — Forward JWTs
Works for simple request chains. Breaks completely for scheduled tasks and async flows. There's no token to forward when a scheduler fires at 3am.
Option 2 — RequestContextHolder
Thread-local storage. Disappears the moment execution becomes async or event-driven. Unreliable by design.
Option 3 — Plain X-User-Id headers
Looks clean. Is dangerous. Any external caller can send X-User-Id: admin123 and impersonate anyone.
The Solution — Internal Trust Protocol
Instead of propagating JWTs, I designed a lightweight trust model based on network boundaries.
The principle: authenticate once at the edge. Let the private network enforce trust internally.
Caller side:
client.get()
.header("X-User-Id", userId)
.header("X-Internal-Call", "true")
.retrieve()
Receiver side checks both headers. If present, builds SecurityContext directly and skips JWT validation entirely.
Why This Isn't Spoofable
The gateway strips X-Internal-Call from all external traffic. Internal services are not publicly exposed. External callers can never reach downstream services directly.
The trust model becomes:
External → Gateway (JWT validation) → Internal Services (trust headers)
The network boundary itself is part of the security model.
Results
Scheduled jobs call services safely. Async flows work. Services stay loosely coupled. Authentication complexity lives exactly where it belongs — at the edge.
Full architecture diagram, comparison table, and complete code walkthrough:
👉 https://anupamkushwaha.me/blog/how-i-secured-internal-microservice-calls-without-passing-jwts
Top comments (0)