User login, Cognito form auth, and LinkedIn: USER_AUTHENTICATION.md.
How it works now:
Frontend User → Gets JWT token (user identity)
↓
backend-actor → Forwards user JWT to actor-service
↓
actor-service → Validates user JWT (knows it's from a user)
What the receiving service knows:
Attack:
document-service with the stolen user tokendocument-service validates the token → ✅ Valid user tokendocument-service processes the request → ❌ But it doesn’t know this isn’t coming from backend-actorCurrent system: Can’t distinguish between:
backend-actor forwarding user X’s token → document-servicedocument-service with user X’s tokenProblem:
Example:
// This won't work - no user token available
@Scheduled(every = "1 day")
void cleanupOldResumes() {
documentService.deleteOldResumes(); // ❌ Needs user JWT, but no user!
}
Problem:
parse-service to only accept calls from document-serviceExample:
document-service → parse-service ✅ (should work)
backend-actor → parse-service ❌ (should be blocked, but currently can't)
attacker → parse-service ❌ (should be blocked, but currently can't distinguish)
Service Accounts:
service_id: "document-service")Service Calls:
document-service → Authenticates with Cognito → Gets service JWT
↓
document-service → Calls parse-service with service JWT
↓
parse-service → Validates service JWT → Knows it's from document-service
↓
parse-service → Can check: "Is the caller document-service?" → ✅ Authorize
Scenario: Order service needs to call inventory service
Without service accounts:
With service accounts:
order-service accountservice_id: "order-service"Scenario: Analytics service needs to sync data every hour
Without service accounts:
With service accounts:
analytics-serviceScenario: Only specific services should access sensitive endpoints
Without service accounts:
admin-service endpoints to only admin-serviceWith service accounts:
admin-service endpoints check: “Is caller admin-service?”Service accounts are created in Cognito using the seed script (scripts/aws/sandbox-cognito-seed.sh):
service-{service-name} (e.g., service-document-service)custom:service_id = {service-name}.envrc1. Service starts up
↓
2. CachingServiceTokenProvider initializes (if credentials configured)
↓
3. Service makes REST client call
↓
4. `UserTokenClientRequestFilter` runs → forwards user token if present
↓
5. `ServiceTokenClientRequestFilter` runs → adds service token if no user token
↓
6. Receiving service receives request with service JWT
↓
7. TokenAuthenticationFilter validates token → detects custom:service_id claim
↓
8. Stores authenticatedServiceId in request context
↓
9. ServiceTokenAuthorizationInterceptor checks @AllowedServices annotation
↓
10. Request proceeds if service is authorized ✅
Domain Interfaces:
ServiceAuthenticationProvider - Authenticate servicesServiceTokenProvider - Get and cache service JWTsInfrastructure:
CognitoServiceAuthenticationProvider - Authenticates services with CognitoCachingServiceTokenProvider - Caches and refreshes service JWTs automaticallyInfrastructure:
ServiceTokenClientRequestFilter - Automatically injects service JWTs into outgoing REST client callsServiceTokenAuthorizationInterceptor - Enforces @AllowedServices restrictions@AllowedServices - Annotation to restrict endpoints to specific servicesServices need the following configuration to enable service-to-service authentication:
cognito.service-account.username=service-document-service
cognito.service-account.password=<password-from-parameter-store>
quarkus.application.name=document-service
These are automatically set by the Cognito seed script in AWS Parameter Store and .envrc.
This implementation provides the foundation for zero-trust architecture:
✅ Every service call is authenticated - Services must have valid JWTs ✅ Service identity verification - Receiving services know which service is calling ✅ Service-level authorization - Fine-grained control over which services can access endpoints ✅ No trusted network assumptions - Services verify each other’s identity regardless of network location ✅ Credential isolation - Service credentials are separate from user credentials ✅ Automatic token management - Tokens are cached and refreshed automatically
What’s in place:
Potential future enhancements: