feat: Adds opt-in X-User-Id header to responses#22689
feat: Adds opt-in X-User-Id header to responses#22689jason-p-pickering wants to merge 25 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces an opt-in session identification mechanism for reverse proxy request correlation by adding an encrypted X-Session-ID response header for authenticated users.
- Implements
SessionIdHeaderFilterthat encryptsuserUid:hash(sessionId)using AES-GCM - Adds two configuration keys:
logging.session_id_header.enabled(default off) andlogging.session_id_encryption_key - Includes integration test that validates encryption/decryption roundtrip
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| SessionIdHeaderFilter.java | New servlet filter that encrypts and adds X-Session-ID header to responses for authenticated users with active sessions |
| SessionIdHeaderFilterTest.java | Integration test that validates the filter encrypts user UID and session hash correctly |
| DhisWebApiWebAppInitializer.java | Registers the new sessionIdHeaderFilter in the servlet filter chain |
| ConfigurationKey.java | Adds two configuration keys for enabling the feature and storing the encryption secret |
| HttpClientAdapter.java | Adds convenience method getHeader() to access response headers in tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
teleivo
left a comment
There was a problem hiding this comment.
How is this different to the hashed version of the Session ID we put into the Mapped Diagnostic Context? Ideally we put this value into the MDC in one place (filter) and use it in logging and elsewhere. We do this for RequestId for example.
Logging should not need a config in the dhis.conf IMHO. Users can configure logging via log4j2.xml and its MDC support like so request_id=%X{xRequestID}.
One detail on the current session id logging: the config name is misleading
|
- SessionIdFilter now always populates MDC for authenticated requests; logging output is controlled via log4j2 pattern layout, not dhis.conf. Reviewer: 'Logging should not need a config in the dhis.conf IMHO.' - X-Session-ID header emission remains opt-in via logging.session_id_header.enabled (reverse proxy boundary feature). - Remove the now-unused logging.session_id ConfigurationKey. - Fix broken merge state: restore missing excludableShallowEtagHeaderFilter registration in DhisWebApiWebAppInitializer. - Update SessionIdFilterTest to capture MDC during filter chain execution (MDC is correctly cleared in the finally block). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Conflict resolutions: - ConfigurationKey.java: drop LOGGING_QUERY_METHOD (removed from master in #23040), keep PR additions (LOGGING_SESSION_ID_HEADER_ENABLED, LOGGING_USER_ID_HEADER_ENABLED, LOGGING_USER_ID_ENCRYPTION_KEY) - DhisWebApiWebAppInitializer.java: drop AppHtmlNoCacheFilter registration (not in current master), keep userIdHeaderFilter (PR feature) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Thanks for the review. Both points addressed in the last push. SessionIdFilter now always populates the sessionId MDC key for authenticated requests (same as RequestIdFilter does for xRequestID). The same hashed value is then optionally emitted as the X-Session-ID response header. On logging config in dhis.conf: Agreed. The logging.request_id.enabled / logging.session_id config gate on MDC has been removed. MDC is always populated; users control whether it appears in log output via their log4j2 pattern layout (%X{sessionId}). The only dhis.conf knob remaining is logging.session_id_header.enabled, which controls the reverse-proxy header. That has nothing to do with logging per se and requires opt-in in the config. On the misleading config name: logging.request_id.enabled had already been renamed to logging.session_id on master. Since the MDC gate is now gone entirely, we've removed that key from ConfigurationKey altogether. To be explicit about the two features and why both exist:
|
This reverts commit 8e2d0c3.
|
|



Adds two opt-in response headers for reverse-proxy request/user correlation.
Background
Correlating user actions with requests is an ongoing challenge for system administrators investigating performance issues: which user is responsible, how is their account configured, which client are they using? There is currently no easy way to answer this at the network boundary. By emitting an encoded user identifier in the response, a reverse proxy can capture it in its access log and correlate requests back to a DHIS2 user for diagnostic purposes.
What this adds
UserIdFilter — emits an X-User-ID response header containing the authenticated user's UID encrypted with AES-256-GCM. Requires configuration (off by default).
SessionIdFilter — extended to optionally emit an X-Session-ID response header containing the same SHA-256-hashed session ID already written to MDC (%X{sessionId}). The MDC is always populated for authenticated requests regardless of this setting; the header is opt-in.
Configuration
Emit encrypted user UID as X-User-ID response header (default: off)
logging.user_id_header.enabled = on
Base64-encoded 32-byte AES-256 key (generate with: openssl rand -base64 32)
logging.user_id_encryption_key =
Emit hashed session ID as X-Session-ID response header (default: off)
logging.session_id_header.enabled = on
Algorithm details
X-User-ID: AES-256-GCM, random 12-byte IV per encryption, output format v1.<base64url(IV + ciphertext)>. The value is cached in-JVM for 5 minutes to avoid re-encrypting on every request. Anyone with access to the proxy logs and the encryption key can recover the user UID. The key should obviously be kept safe.X-Session-ID: SHA-256 hash of the session ID (base64-encoded), same value as MDC sessionId. Useful for correlating proxy log lines with DHIS2 log lines, but not reversible.