ADR-027: X509 Header-Based Authentication
Statusβ
Accepted
Dateβ
2026-01-31
Contextβ
STOA supports mTLS for API client authentication (RFC 8705 Certificate-Bound Access Tokens). In production, TLS is terminated at the edge load balancer (F5) β not at Keycloak. Keycloak must therefore validate client certificates received via HTTP headers injected by the trusted proxy.
Related Decisionsβ
- ADR-026: Multi-IAM Federation β mTLS between components
- ADR-011: API Security Modes β mTLS for CORE internal APIs
- CAB-865: Client certificate provisioning
- CAB-866: Keycloak certificate sync (x509.certificate.sha256 attributes)
Decisionβ
Use Keycloak's built-in x509cert-lookup SPI with the nginx provider mode. No custom Java SPI is required.
Architectureβ
Client (mTLS) βββ F5 / Nginx (terminates TLS) βββ Keycloak (reads headers)
β
ββ Injects: SSL_CLIENT_CERT, SSL_CLIENT_VERIFY,
SSL_CLIENT_S_DN, SSL_CLIENT_FINGERPRINT
Header Contract (F5 β Keycloak)β
| Header | Content | Format |
|---|---|---|
SSL_CLIENT_CERT | Client certificate | URL-encoded PEM |
SSL_CLIENT_CERT_CHAIN_0 | Issuing CA certificate | URL-encoded PEM |
SSL_CLIENT_VERIFY | Verification result | SUCCESS / FAILED / NONE |
SSL_CLIENT_S_DN | Subject Distinguished Name | CN=client-123.client.stoa.internal |
SSL_CLIENT_FINGERPRINT | SHA-256 fingerprint | Hex string |
Authentication Flowβ
The x509-client-credentials flow uses:
- X509 authenticator (ALTERNATIVE, priority 10) β matches certificate fingerprint against
x509.certificate.sha256client attribute (set by CAB-866) - Client secret (ALTERNATIVE, priority 20) β fallback for clients without certificates
Configurationβ
Keycloak environment variables:
KC_SPI_X509CERT_LOOKUP_PROVIDER=nginx
KC_SPI_X509CERT_LOOKUP_NGINX_SSL_CLIENT_CERT=SSL_CLIENT_CERT
KC_SPI_X509CERT_LOOKUP_NGINX_SSL_CERT_CHAIN_PREFIX=SSL_CLIENT_CERT_CHAIN
KC_SPI_X509CERT_LOOKUP_NGINX_CERTIFICATE_CHAIN_LENGTH=1
Security Considerationsβ
- Trusted Proxy Only: A Kubernetes
NetworkPolicyrestricts which sources may send X509 headers to Keycloak. Without this, any pod could spoofSSL_CLIENT_CERT. - Header Validation: Keycloak validates the certificate fingerprint against the registered client's
x509.certificate.sha256attribute. - Audit Logging: Keycloak logs all authentication attempts. Failed X509 auth (missing/invalid cert) is logged at WARN level.
- No PEM in Logs: Only fingerprints are logged; full certificate PEM is never written to logs.
Consequencesβ
Positiveβ
- No custom Java code to build, test, or maintain
- Uses battle-tested Keycloak SPI (supported since Keycloak 4.x)
- Compatible with any TLS-terminating proxy (F5, nginx, HAProxy, AWS ALB)
- Graceful fallback to client_secret for non-mTLS clients
Negativeβ
- Depends on correct proxy configuration (wrong header name = silent auth failure)
- Network policy is critical β misconfiguration allows header spoofing
Mitigationsβ
- Nginx dev proxy mimics F5 headers for local testing
- Integration tests validate all auth scenarios (valid cert, invalid, missing, expired, revoked)
- NetworkPolicy is mandatory (enforced via Helm values)