mirror of
https://github.com/azaion/missions.git
synced 2026-06-21 09:51:07 +00:00
refactor: enhance JWT authentication and CORS configuration
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.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# 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 <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_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 <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
|
||||
|
||||
```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 <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.
|
||||
Reference in New Issue
Block a user