Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
5.2 KiB
Flow F5 — JWT bearer validation
Cross-cutting flow that runs on every
[Authorize]request. Local validation only — this service never calls back to the issuingadminservice.
Description
ASP.NET Core's JwtBearerHandler validates incoming Authorization: Bearer <jwt> headers against the shared HMAC secret (JWT_SECRET). On success, the request continues to the controller with a ClaimsPrincipal attached. On signature / lifetime failure → 401. On valid token but missing "FL" permission claim → 403. The iss / aud claims are intentionally NOT validated today (CMMC L2 finding tracked at suite level under AZ-487 / AZ-494 — see 05_identity § Implementation Details).
Preconditions
JWT_SECRETis resolved at startup (env or hardcoded dev fallback perarchitecture.mdADR-005).AddJwtAuth(jwtSecret)was called duringProgram.csstartup (F6).
Sequence Diagram
sequenceDiagram
autonumber
participant Client as UI / Operator API client
participant Pipeline as ASP.NET Pipeline
participant Handler as JwtBearerHandler
participant Policy as Auth policy "FL"
participant Ctrl as Feature Controller
participant Errs as 06_http_conventions
Client->>Pipeline: HTTP request + Authorization: Bearer <jwt>
Pipeline->>Errs: enter ErrorHandlingMiddleware
Errs->>Handler: hand off (anonymous endpoints skip this)
Handler->>Handler: parse token; verify HMAC-SHA256 signature using SymmetricSecurityKey(UTF-8(JWT_SECRET))
alt Signature invalid OR token expired (ClockSkew = 1 minute)
Handler-->>Client: 401 Unauthorized
else Valid token
Handler->>Handler: build ClaimsPrincipal; skip iss/aud validation
Handler->>Policy: evaluate policy "FL" (requires permissions claim == "FL")
alt Claim missing or != "FL"
Policy-->>Client: 403 Forbidden
else permissions=FL
Policy-->>Ctrl: forward to controller action
Ctrl-->>Client: business response
end
end
Flowchart
flowchart TD
Start([Incoming request]) --> AnonEP{Endpoint requires auth?}
AnonEP -->|no| Forward([Forward to controller])
AnonEP -->|yes| Header{Authorization: Bearer present?}
Header -->|no| Unauth1([401 Unauthorized])
Header -->|yes| Sig{HMAC-SHA256 signature valid?}
Sig -->|no| Unauth2([401 Unauthorized])
Sig -->|yes| Life{Lifetime valid? ClockSkew=1min}
Life -->|no| Unauth3([401 Unauthorized — expired])
Life -->|yes| BuildPrincipal[Build ClaimsPrincipal — skip iss/aud]
BuildPrincipal --> Policy{permissions claim == FL?}
Policy -->|no| Forbid([403 Forbidden])
Policy -->|yes| Forward
Data Flow
| Step | From | To | Data | Format |
|---|---|---|---|---|
| 1 | Client | Pipeline | Authorization: Bearer <jwt> header |
HTTP header |
| 2 | JwtBearerHandler |
(in-process) | parsed JWT (header, payload, signature) | JSON Web Token |
| 3 | JwtBearerHandler |
(in-process) | ClaimsPrincipal |
.NET principal object |
| 4 | Authorization policy evaluator | Controller | "policy satisfied" / 403 |
flag |
| 5 | JwtBearerHandler |
Client (only on failure) | 401 / 403 |
HTTP status (no body) |
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
Missing Authorization header on [Authorize] route |
JwtBearerHandler |
Header absent | 401. Client must obtain a token from admin |
| Malformed JWT | JwtBearerHandler |
Token parse failure | 401 |
Signature mismatch (wrong / rotated JWT_SECRET) |
JwtBearerHandler |
HMAC verify fails | 401. Suite-wide secret rotation is coordinated re-deploy of every backend that shares the secret + UI re-login |
| Expired token | JwtBearerHandler |
ValidateLifetime = true (ClockSkew = 1 min) |
401. Tighter than .NET's 5-min default — caller may experience earlier expiration than expected |
permissions claim missing or wrong value |
Policy "FL" evaluator |
claim lookup | 403 |
| Token signed with the well-known dev fallback secret | (silent acceptance) | None | Security risk in production. ADR-005 carry-forward; suite-tracked under CMMC L2 row 3 |
Token from a third-party that knows JWT_SECRET |
(silent acceptance) | None | Trust model is shared-secret intra-suite. Any third-party with the secret can mint accepted tokens. Out of this Epic's scope; suite-wide concern |
Performance Expectations
| Metric | Target | Notes |
|---|---|---|
| Validation latency | sub-millisecond typical | Pure HMAC + claim lookup; no I/O, no network call |
| Throughput | bounded by request throughput | No back-pressure; no token cache (no DB / network round-trip to cache) |
Notes on iss / aud validation (suite-tracked)
ValidateIssuer = false, ValidateAudience = false — consistent with the shared-secret intra-suite model. The CMMC L2 scorecard (../../../suite/_docs/05_security/cmmc_l2_scorecard.md row 3) flags this as a finding. The remediation will copy the satellite-provider pattern across annotations and missions (suite work, AZ-487 / AZ-494). It is NOT in this Epic's scope and will not change as part of the rename refactor.