When services call other services, how does the system enforce who can access what? Token-based authorization frameworks are a proven approach for cloud ecosystems with many microservices. Here is the architecture and the tradeoffs.
The Problem
In large-scale environments, dozens of microservices communicate over REST. Each service may have its own authentication mechanism: some use API keys, others use shared secrets, a few use nothing. There is no consistent way to answer: "Is Service A allowed to call this endpoint on Service B?"
The Design
A token-based system typically has three components:
- Token issuer: A central service that mints short-lived JWTs. Each token encodes the calling service's identity and its allowed scopes.
- Token validator: A shared library (packaged as a Spring Boot starter) that every service includes. It validates the JWT signature, checks expiry, and verifies the scope matches the endpoint being called.
- Scope registry: A YAML configuration per service declaring which scopes it requires for each endpoint group.
Least-Privilege in Practice
The key design decision is making scopes granular. Instead of a single service-a:read scope, define service-a:orders:read and service-a:inventory:read. This means Service B can access orders but not inventory, even though both are on Service A.
Backward compatibility matters. When introducing token-based auth alongside a legacy system, services should accept both auth methods for a defined transition period. After monitoring confirms all traffic uses tokens, remove the legacy paths.
What Breaks
Clock skew. JWTs have an exp claim. If the issuing server's clock is slightly ahead of the validating server, tokens appear expired. A small grace window (a few seconds) feels wrong but is necessary in a distributed system where NTP drift is real.