# Flow F5 — JWT bearer validation > Cross-cutting flow that runs on every `[Authorize]` request. Local validation only — this service never calls back to the issuing `admin` service. ## Description ASP.NET Core's `JwtBearerHandler` validates incoming `Authorization: Bearer ` 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_SECRET` is resolved at startup (env or hardcoded dev fallback per `architecture.md` ADR-005). - `AddJwtAuth(jwtSecret)` was called during `Program.cs` startup (F6). ## Sequence Diagram ```mermaid 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 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 ```mermaid 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 ` 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.