Layering Session Authentication with Internal JWT for Secure Microservice Communication
How I layered internal JWT authentication on top of Passport session-based auth to securely connect a Node.js monolith with Spring Boot microservices in my LinkedUp architecture.
introduction
While building LinkedUp, I initially used Passport.js with express-session for user authentication. This worked well for browser-to-server authentication. However, once I introduced Spring Boot microservices, a new problem emerged: how do services trust each other securely without exposing public endpoints or implementing full OAuth?
problem statement
Passport sessions authenticate users at the gateway layer, but microservices cannot directly trust browser sessions. Passing session cookies to internal services is insecure and tightly couples services to the gateway. I needed a secure, scalable method for service-to-service authentication without rewriting the entire auth system.
threat model
{
"forged_internal_requests": "An attacker attempting to call microservice endpoints directly.",
"service_spoofing": "Unauthorized services pretending to be trusted internal services.",
"session_reuse": "Improper propagation of user session cookies across services.",
"privilege_escalation": "Users accessing protected microservice routes without proper validation."
}naive approach and failure
One approach would be forwarding session cookies from the Express gateway to Spring services. This tightly couples services and exposes internal session logic. Another option was implementing OAuth immediately, which would add unnecessary complexity for an internal architecture. Both approaches were either insecure or operationally heavy.
design decision
{
"architecture_pattern": "Gateway-Issued Internal JWT",
"core_idea": "Keep Passport sessions for browser authentication, but issue short-lived internal JWTs for service-to-service communication.",
"token_scope": "Only internal write routes require JWT validation."
}architecture overview
The Node.js gateway authenticates the user using Passport sessions. When calling a Spring Boot microservice, the gateway generates a short-lived internal JWT containing the user ID (sub claim). The Spring service validates this JWT using a shared secret and sets the SecurityContext accordingly. Only specific internal routes require this validation.
implementation details
{
"jwt_generation": "The Express gateway signs a short-lived JWT (e.g., 5 minutes) using a shared secret and includes the user ID in the payload.",
"jwt_validation": "Spring Boot uses a custom filter that validates the token and sets authentication in the SecurityContext.",
"route_scoping": "Spring SecurityConfig defines which routes require internal JWT validation.",
"cors_handling": "OPTIONS requests are excluded from JWT validation to avoid CORS issues."
}why not oauth
- Internal services do not require third-party authorization flows.
- OAuth introduces token refresh complexity.
- Operational overhead was unnecessary for controlled internal communication.
security strengths
- Clear separation between browser authentication and service authentication.
- Short-lived tokens reduce risk window.
- Microservices do not depend on session storage.
- Improved trust boundaries between components.
tradeoffs
{
"pros": [
"Simple and controlled internal trust model.",
"Minimal architectural disruption.",
"Stateless verification on microservices."
],
"cons": [
"Shared secret must be securely managed.",
"Requires careful route scoping.",
"Adds additional token issuance step."
]
}lessons learned
- Authentication and authorization boundaries must evolve with architecture.
- Microservices require explicit trust mechanisms.
- Security decisions should match system scale, not trends.
- Layered authentication improves maintainability.
future improvements
- Rotate internal JWT secrets periodically.
- Move to asymmetric signing (public/private key) for larger scale.
- Introduce service identity verification for multi-region deployments.
conclusion
By layering internal JWT authentication on top of Passport sessions, I created a secure and scalable trust boundary between my Node.js gateway and Spring Boot microservices. This approach avoided unnecessary OAuth complexity while maintaining strong internal security guarantees. It allowed LinkedUp to evolve into a hybrid microservice architecture without compromising authentication integrity.