API-Key Injection at the Gateway
1. Overview
admin-service requires every request that arrives at /auth/ or at any portal-originated endpoint to carry a valid X-API-KEY header. The key authenticates the caller — specifically the portal gateway, not the user. User identity is carried separately, via the JWT on /api/ or via the claims in the token-exchange DTO.
The API key is held only on the gateway and only in two places: Spring Boot configuration (from a Kubernetes secret or environment variable) and a single @Value injection. It never enters Angular code, never reaches the browser, never appears in a log line.
This page covers how the key is injected on proxied requests, where it comes from, and how rotation works.
See also: Portal Pattern.
2. Why the API Key Exists
admin-service trusts the portal, not the browser. This trust is established by the portal proving ownership of a shared secret (the API key) on the subset of endpoints where browser identity alone is not sufficient:
-
POST /auth/token-exchange/oauth2— accepts validated OIDC claims from the portal; admin-service must know that the claims came from a portal that already validated the IdP signature, not from an attacker posting arbitrary JSON. -
POST /auth/external-login— accepts userKey + userHash; the portal proves it is allowed to submit external-auth attempts. -
POST /auth/register-session— anonymous UUID exchange; portal proves it is a legitimate anonymous-flow front-door. -
Any admin-service endpoint that accepts a request on behalf of the portal rather than a specific user.
The API key is admin-service’s answer to "who is calling me, and do I trust them enough to treat these claims as pre-validated?"
3. Injection Mechanisms
Portal gateways inject X-API-KEY in two places:
3.1. Spring Cloud Gateway route filter (proxied /services/admin-service/**)
Configured declaratively in application.yml:
spring:
cloud:
gateway:
mvc:
routes:
- id: admin-service
uri: ${application.admin-service.url}
predicates:
- Path=/services/admin-service/**
filters:
- RewritePath=/services/admin-service/(?<segment>.*), /$\{segment}
- AddRequestHeader=X-API-KEY, ${application.admin-service.api-key}
Effect: every request under /services/admin-service/** is rewritten to drop the prefix and has X-API-KEY added before being proxied. The browser neither sees the key nor sees the target URL — the browser only talks to its own origin.
Equivalent programmatic form (RouteLocator bean) is available if more complex logic is needed; prefer the YAML form when the policy is static.
3.2. RestTemplate / WebClient for portal-initiated calls
The portal also makes direct server-to-server calls for token-exchange, cache invalidation, etc. These live in @Component classes like AdminServiceAuthClient and BackendTokenExchangeClient. Injection is explicit:
@Component
public class AdminServiceAuthClient {
private final String apiKey;
public AdminServiceAuthClient(
RestTemplate restTemplate,
@Value("${application.admin-service.url:http://localhost:12504}") String adminServiceUrl,
@Value("${application.admin-service.api-key}") String apiKey
) {
this.apiKey = apiKey;
// ...
}
public String exchangeForJwt(...) {
HttpHeaders headers = new HttpHeaders();
headers.set("X-API-KEY", apiKey);
// ...
}
}
Reference: [registration-portal/…/service/AdminServiceAuthClient.java](registration-portal/src/main/java/za/co/idealogic/registration/ui/service/AdminServiceAuthClient.java).
The key is never passed as a method parameter across callers — it is injected once at construction and used privately. This prevents accidental logging via @Slf4j on a higher-level service that might print its arguments.
4. Configuration Surface
The key lives in the portal’s Spring config under:
application:
admin-service:
url: http://prod-event-admin-service
api-key: ${ADMIN_SERVICE_API_KEY} # resolved from env or K8s secret
Sourced in production via Helm-templated Kubernetes secret (see Helm Chart). Never committed to git.
Naming in the current codebase: the Helm chart surfaces it as config.services.apikey (see registration-portal Helm values). When admin-portal lands, follow the same key path so operators can reuse their mental model.
5. Rotation Procedure
Because the key is deployment-managed and not per-user, rotation is a deployment concern, not a user-facing one.
Rotate via rolling update:
-
Generate a new key (any random 32+ char value).
-
Update the admin-service config to accept both old and new keys for a short overlap window. This requires admin-service’s
ApiKeyFilterto support a multi-key list; if it currently supports only one key, add the overlap capability before the first rotation. -
Roll admin-service pods to pick up the dual-key config.
-
Update each portal’s Helm values to use the new key.
-
Roll each portal’s pods.
-
Once all portals are on the new key, remove the old key from admin-service’s config and roll admin-service again.
Total downtime: zero, assuming overlap support exists. If overlap support does not exist yet, it becomes a prerequisite ticket. Flag as an open item for WS5 / WS7.
5.1. Emergency rotation
If the key is known-compromised:
-
Generate a new key.
-
Deploy admin-service with only the new key — rejecting all in-flight requests carrying the old key.
-
Deploy all portals with the new key in parallel. Expect a brief window of portal-level 401s; portals retry or users re-authenticate.
Emergency rotation accepts a few minutes of downtime in exchange for closing the compromised key immediately.
6. What Not To Do
-
Do not expose the API key to the Angular SPA. Not as
environment.ts, not as a webpack-injected constant, not as a cookie. Anything reachable from browser JavaScript is assumed public. -
Do not log the API key. Spring’s default configuration property masking should cover
application.admin-service.api-key; verify with aGET /management/envcheck in a dev environment. -
Do not reuse the same API key across unrelated deployments (prod + stage + dev). Use a per-environment key; generate fresh on each rotation.
-
Do not use the API key as a general authentication mechanism. It authenticates the portal to admin-service. User identity is separate (JWT or OIDC claims).
-
Do not accept the key from a request header on portal-local endpoints. The key flows portal → admin-service, never browser → portal.
7. Admin-portal Specifics
admin-portal’s Spring Cloud Gateway configuration is identical to registration-portal’s in shape — same /services/admin-service/** route, same AddRequestHeader=X-API-KEY. The only differences:
-
Separate key per portal (optional — admin-portal and registration-portal can use different keys for blast-radius reduction). Recommended when keys are cheap to manage (Kubernetes secrets).
-
admin-portal does not call
/auth/external-loginor/auth/register-session— only/auth/token-exchange/oauth2for login + tenant switch. TheAdminServiceAuthClientequivalent in admin-portal can be leaner.
8. Reference Implementation
| File | Role |
|---|---|
|
Gateway route config with |
|
Example programmatic injection in a RestTemplate client |
|
The admin-service side — validates incoming |
|
Which admin-service paths require the API key |