Skip to content

fix(service-defaults): security trio — middleware order, JWT, error t…#25

Merged
emeraldleaf merged 4 commits into
mainfrom
fix/service-defaults-security-trio
May 24, 2026
Merged

fix(service-defaults): security trio — middleware order, JWT, error t…#25
emeraldleaf merged 4 commits into
mainfrom
fix/service-defaults-security-trio

Conversation

@emeraldleaf
Copy link
Copy Markdown
Owner

@emeraldleaf emeraldleaf commented May 24, 2026

…race ID

Three small but distinct security hardening fixes in ServiceDefaults (addresses architecture-review findings #3, #5, #6).

  1. MIDDLEWARE ORDER — CorrelationIdMiddleware after UseAuthentication

The middleware reads ClaimTypes.NameIdentifier from context.User to populate the UserId logger scope key. Pre-auth, context.User is empty, so UserId was silently always null for every authenticated request — defeating half the context-propagation pipeline. UserId never made it into log scopes from the HTTP entry point and never reached Wolverine envelope headers via the ActivityBaggage → OutgoingContextMiddleware path.

Reordered: UseExceptionHandler → UseAuthentication → UseAuthorization → CorrelationIdMiddleware. ExceptionHandler stays first so it still wraps everything below it.

  1. JWT — explicit ValidateIssuerSigningKey + tightened ClockSkew

ValidateIssuerSigningKey was implicit (JWT Bearer's default validates against JWKS-discovered keys). Made explicit so it's auditable + prevents a future config change from silently disabling it.

ClockSkew defaulted to 5 MINUTES. Revoked/expired tokens were accepted for 5 extra minutes — material on typical 15-minute access-token lifetimes. Set to 30 seconds: covers reasonable inter-server clock drift, doesn't give attackers a long replay window.

  1. EXCEPTION HANDLER — TraceId only, not full Activity.Id

Activity.Current?.Id returns the full W3C traceparent ("00---") and we were embedding that in every ProblemDetails response. The span ID is information about server-side handler call structure that clients have no business seeing. Switched to Activity.Current?.TraceId.ToString() — same correlation utility, no leak of internal call graph.

None of the three needed any consumer changes; all are internal to ServiceDefaults. Build clean (the one CS0436 warning is the pre-existing benchmarks Program-conflict; unaffected).

What changed

How it was built

Verification

Touches (check only if applies)

  • EF Core migration (immutable once applied — see CLAUDE.md "Performance Rules")
  • CLAUDE.md or a See CLAUDE.md paraphrase (run /check-rules to audit drift)

Summary by CodeRabbit

  • Security Improvements

    • Stricter JWT validation: enforced signing-key checks and reduced replay window (30s)
    • Error responses hide sensitive span info, returning only a trace identifier
  • Bug Fixes

    • Adjusted middleware ordering so authentication context is available for correlation and authorization
  • Tests

    • Added unit tests verifying traceId behavior, error masking, and JWT validation defaults

Review Change Stack

…race ID

Three small but distinct security hardening fixes in ServiceDefaults
(addresses architecture-review findings #3, #5, #6).

1. MIDDLEWARE ORDER — CorrelationIdMiddleware after UseAuthentication

The middleware reads ClaimTypes.NameIdentifier from context.User to populate
the UserId logger scope key. Pre-auth, context.User is empty, so UserId was
silently always null for every authenticated request — defeating half the
context-propagation pipeline. UserId never made it into log scopes from the
HTTP entry point and never reached Wolverine envelope headers via the
ActivityBaggage → OutgoingContextMiddleware path.

Reordered: UseExceptionHandler → UseAuthentication → UseAuthorization →
CorrelationIdMiddleware. ExceptionHandler stays first so it still wraps
everything below it.

2. JWT — explicit ValidateIssuerSigningKey + tightened ClockSkew

ValidateIssuerSigningKey was implicit (JWT Bearer's default validates
against JWKS-discovered keys). Made explicit so it's auditable + prevents
a future config change from silently disabling it.

ClockSkew defaulted to 5 MINUTES. Revoked/expired tokens were accepted for
5 extra minutes — material on typical 15-minute access-token lifetimes.
Set to 30 seconds: covers reasonable inter-server clock drift, doesn't
give attackers a long replay window.

3. EXCEPTION HANDLER — TraceId only, not full Activity.Id

Activity.Current?.Id returns the full W3C traceparent
("00-<trace>-<span>-<flags>") and we were embedding that in every
ProblemDetails response. The span ID is information about server-side
handler call structure that clients have no business seeing. Switched to
Activity.Current?.TraceId.ToString() — same correlation utility, no leak
of internal call graph.

None of the three needed any consumer changes; all are internal to
ServiceDefaults. Build clean (the one CS0436 warning is the pre-existing
benchmarks Program-conflict; unaffected).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 106b61ea-c59d-4324-aeb9-59e1d1dd6b79

📥 Commits

Reviewing files that changed from the base of the PR and between 6e2d45c and 27b8a93.

📒 Files selected for processing (2)
  • tests/OrderService.Tests.Unit/Application/GlobalExceptionHandlerTests.cs
  • tests/OrderService.Tests.Unit/Application/ServiceDefaultsJwtTests.cs

Walkthrough

Middleware ordering changed so CorrelationIdMiddleware runs after authentication and before authorization; JWT bearer TokenValidationParameters explicitly enable issuer signing key validation and set ClockSkew to 30s; GlobalExceptionHandler returns Activity.TraceId (or HttpContext.TraceIdentifier) and omits raw exception messages.

Changes

Service Defaults Security and Observability Hardening

Layer / File(s) Summary
Authentication & Correlation pipeline
NextAurora.ServiceDefaults/Extensions.cs, tests/OrderService.Tests.Unit/Application/ServiceDefaultsJwtTests.cs
Reorders middleware so CorrelationIdMiddleware executes after UseAuthentication and before UseAuthorization; updates JWT bearer TokenValidationParameters to set ValidateIssuerSigningKey = true and ClockSkew = TimeSpan.FromSeconds(30) with explanatory comments; adds tests asserting these JWT settings.
Exception handler trace ID extraction
NextAurora.ServiceDefaults/GlobalExceptionHandler.cs, tests/OrderService.Tests.Unit/Application/GlobalExceptionHandlerTests.cs
GlobalExceptionHandler now derives trace identifier from Activity.Current?.TraceId (falling back to HttpContext.TraceIdentifier) to avoid exposing full W3C span ids; adds tests verifying traceId selection and that ProblemDetails do not include raw exception messages.

Sequence Diagram

sequenceDiagram
  participant Client
  participant UseExceptionHandler
  participant UseAuthentication
  participant CorrelationIdMiddleware
  participant UseAuthorization
  participant Endpoint
  Client->>UseExceptionHandler: incoming request
  UseExceptionHandler->>UseAuthentication: pass to authentication
  UseAuthentication->>CorrelationIdMiddleware: authentication completed (context.User available)
  CorrelationIdMiddleware->>UseAuthorization: attach correlation id for audit/authorization
  UseAuthorization->>Endpoint: authorize then route
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main security improvements: middleware reordering, JWT hardening, and exception handler trace ID changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/service-defaults-security-trio

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@NextAurora.ServiceDefaults/Extensions.cs`:
- Around line 144-146: The CorrelationIdMiddleware is registered after
UseAuthorization which allows authorization to short-circuit before the
middleware runs; move the middleware registration so it executes between
UseAuthentication and UseAuthorization by reordering the calls: call
app.UseAuthentication(), then app.UseMiddleware<CorrelationIdMiddleware>(), then
app.UseAuthorization() so that CorrelationIdMiddleware runs for
authenticated/authorization flows and applies correlation headers and scoped
logging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9d4d0281-fb4d-4711-a866-5cf545183f13

📥 Commits

Reviewing files that changed from the base of the PR and between 709f9d9 and 3e4de55.

📒 Files selected for processing (2)
  • NextAurora.ServiceDefaults/Extensions.cs
  • NextAurora.ServiceDefaults/GlobalExceptionHandler.cs

Comment thread NextAurora.ServiceDefaults/Extensions.cs
… Authz

CodeRabbit (PR #25) flagged that the previous fix moved the middleware too
far down — to AFTER UseAuthorization — which works for populating UserId
but drops the audit trail for 401/403 unauthorized-attempt logs.

Correct placement is BETWEEN UseAuthentication and UseAuthorization:

  app.UseExceptionHandler();
  app.UseAuthentication();          // populates context.User
  app.UseMiddleware<CorrelationIdMiddleware>();  // reads User, opens scope
  app.UseAuthorization();           // any 401/403 here has UserId in scope

Two reasons:
  1. CorrelationIdMiddleware needs context.User populated to read
     NameIdentifier (the original ordering bug — pre-auth, User was empty
     and UserId was always null).
  2. Placing it BEFORE Authorization means the UserId scope is active
     during the authorization decision. If a user is rejected, the log
     line says "user X tried Y and was denied" — the audit trail we need.
     My previous fix dropped this by placing the middleware after
     Authorization, which would only attribute requests that actually
     passed authorization.

Build clean. Single-line move of one app.UseMiddleware call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 24, 2026
emeraldleaf and others added 2 commits May 23, 2026 22:13
…andler

Covers the production-code changes in this PR so Codecov's patch-coverage
gate stops complaining (was 8.33%; the prior fixes were uncovered because
ServiceDefaults had no dedicated tests).

GlobalExceptionHandlerTests (3):
  - TryHandleAsync_WhenActivityIsActive_ReturnsTraceIdOnly_NotFullActivityId:
    pins the security-critical change (Activity.Id → TraceId.ToString()).
    Asserts the response traceId equals activity.TraceId.ToString(), NOT
    activity.Id (which is the full W3C traceparent including span ID).
    Also asserts no hyphens — distinguishes the bare trace from the full
    "00-trace-span-flags" format.
  - TryHandleAsync_WhenNoActivity_FallsBackToHttpContextTraceIdentifier:
    covers the null-coalesce path.
  - TryHandleAsync_AlwaysReturnsProblemDetailsWithGenericDetail_NeverExceptionMessage:
    defense against information disclosure (CLAUDE.md "Error Handling").
    Asserts the raw exception message doesn't appear in the response body
    even when the message contains sensitive-looking content like SQL.

ServiceDefaultsJwtTests (3):
  - AddServiceDefaults_WhenAuthorityConfigured_SetsExplicitSigningKeyValidation
  - AddServiceDefaults_WhenAuthorityConfigured_SetsTightClockSkew
  - AddServiceDefaults_WhenAuthorityConfigured_RetainsCoreValidations
  All three boot a real Host.CreateApplicationBuilder + AddServiceDefaults
  with a configured authority, resolve the IOptionsMonitor<JwtBearerOptions>,
  and assert the TokenValidationParameters. Pins both the new hardening
  (ClockSkew=30s, ValidateIssuerSigningKey=true) AND the pre-existing core
  validations (Audience/Issuer/Lifetime) so a future refactor can't drop
  either silently.

All 6 tests pass in 0.7s. Build clean (the one CS0436 warning is the
pre-existing benchmarks Program-conflict; unaffected).

Test placement follows the existing convention — ServiceDefaults tests
live in service-test projects (OrderService.Tests.Unit/Application/
already has ContextPropagationMiddlewareTests, CorrelationIdMiddlewareTests,
OutgoingContextMiddlewareTests). Not ideal long-term (duplication risk
across the 4 service test projects), but matches the established pattern.

The middleware-ordering change in Extensions.cs is harder to unit-test —
it requires booting WebApplicationFactory and asserting the order of
middleware execution at request time. Filed as a project-board issue for
later; the JWT and ExceptionHandler tests cover the highest-risk lines
of this PR (security hardening that could silently regress).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@emeraldleaf emeraldleaf merged commit 6f0242f into main May 24, 2026
7 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request May 24, 2026
2 tasks
@emeraldleaf emeraldleaf deleted the fix/service-defaults-security-trio branch June 4, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant